1use std::collections::{HashMap, HashSet};
20use std::sync::Arc;
21
22use api::helper::ColumnDataTypeWrapper;
23use api::v1::value::ValueData;
24use api::v1::{ColumnSchema, Rows, SemanticType};
25use arrow::array::{
26 ArrayRef, ArrowPrimitiveType, Float64Builder, PrimitiveBuilder, StringBuilder, new_null_array,
27};
28use arrow::datatypes::{
29 DataType as ArrowDataType, Schema as ArrowSchema, TimestampMicrosecondType,
30 TimestampMillisecondType, TimestampNanosecondType, TimestampSecondType,
31};
32use arrow::record_batch::RecordBatch;
33use arrow_schema::TimeUnit;
34use common_query::prelude::{greptime_timestamp, greptime_value};
35use common_time::Timestamp;
36use common_time::timestamp::TimeUnit as CommonTimeUnit;
37use datatypes::data_type::DataType;
38use datatypes::prelude::ConcreteDataType;
39use snafu::{OptionExt, ResultExt, ensure};
40
41use crate::batcher::logical_table::RecordBatchWithTsIdx;
42use crate::error;
43use crate::error::Result;
44
45fn unzip_logical_region_schema(
47 target_schema: &ArrowSchema,
48) -> Result<(String, String, HashSet<String>)> {
49 let mut timestamp_column = None;
50 let mut field_column = None;
51 let mut tag_columns = HashSet::with_capacity(target_schema.fields.len().saturating_sub(2));
52 for field in target_schema.fields() {
53 if field.name() == greptime_timestamp() {
54 timestamp_column = Some(field.name().clone());
55 continue;
56 }
57
58 if field.name() == greptime_value() {
59 field_column = Some(field.name().clone());
60 continue;
61 }
62
63 if timestamp_column.is_none() && matches!(field.data_type(), ArrowDataType::Timestamp(_, _))
64 {
65 timestamp_column = Some(field.name().clone());
66 continue;
67 }
68
69 if field_column.is_none() && matches!(field.data_type(), ArrowDataType::Float64) {
70 field_column = Some(field.name().clone());
71 continue;
72 }
73 tag_columns.insert(field.name().clone());
74 }
75
76 let timestamp_column = timestamp_column.with_context(|| error::UnexpectedResultSnafu {
77 reason: "Failed to locate timestamp column in target schema".to_string(),
78 })?;
79 let field_column = field_column.with_context(|| error::UnexpectedResultSnafu {
80 reason: "Failed to locate field column in target schema".to_string(),
81 })?;
82
83 Ok((timestamp_column, field_column, tag_columns))
84}
85
86pub(crate) fn rows_to_aligned_record_batch(
90 rows: &Rows,
91 target_schema: &ArrowSchema,
92) -> Result<RecordBatchWithTsIdx> {
93 let row_count = rows.rows.len();
94 let column_count = rows.schema.len();
95
96 for (idx, row) in rows.rows.iter().enumerate() {
97 ensure!(
98 row.values.len() == column_count,
99 error::InternalSnafu {
100 err_msg: format!(
101 "Column count mismatch in row {}, expected {}, got {}",
102 idx,
103 column_count,
104 row.values.len()
105 )
106 }
107 );
108 }
109
110 let (target_ts_name, target_field_name, _target_tags) =
111 unzip_logical_region_schema(target_schema)?;
112 let timestamp_index = target_schema
113 .column_with_name(&target_ts_name)
114 .map(|(index, _)| index)
115 .with_context(|| error::UnexpectedResultSnafu {
116 reason: format!(
117 "Failed to resolve timestamp column '{}' in target schema",
118 target_ts_name
119 ),
120 })?;
121
122 let mut source_map: HashMap<&str, (usize, ArrowDataType)> =
125 HashMap::with_capacity(rows.schema.len());
126
127 for (src_idx, col) in rows.schema.iter().enumerate() {
128 let wrapper = ColumnDataTypeWrapper::try_new(col.datatype, col.datatype_extension.clone())?;
129 let src_arrow_type = ConcreteDataType::from(wrapper).as_arrow_type();
130
131 match &src_arrow_type {
132 ArrowDataType::Float64 => {
133 source_map.insert(&target_field_name, (src_idx, src_arrow_type));
134 }
135 ArrowDataType::Timestamp(_, _) => {
136 source_map.insert(&target_ts_name, (src_idx, src_arrow_type));
137 }
138 ArrowDataType::Utf8 => {
139 source_map.insert(&col.column_name, (src_idx, src_arrow_type));
140 }
141 other => {
142 return error::InvalidPromRemoteRequestSnafu {
143 msg: format!(
144 "Unexpected remote write batch field type {}, field name: {}",
145 other, col.column_name
146 ),
147 }
148 .fail();
149 }
150 }
151 }
152
153 let mut columns = Vec::with_capacity(target_schema.fields().len());
157 for target_field in target_schema.fields() {
158 if let Some((src_idx, src_arrow_type)) = source_map.get(target_field.name().as_str()) {
159 let target_type = if matches!(
160 (src_arrow_type, target_field.data_type()),
161 (
162 ArrowDataType::Timestamp(_, _),
163 ArrowDataType::Timestamp(_, _)
164 )
165 ) {
166 target_field.data_type().clone()
167 } else {
168 src_arrow_type.clone()
169 };
170 let array = build_arrow_array(
171 rows,
172 *src_idx,
173 &rows.schema[*src_idx].column_name,
174 target_type,
175 row_count,
176 )?;
177 columns.push(array);
178 } else {
179 columns.push(new_null_array(target_field.data_type(), row_count));
180 }
181 }
182
183 let batch = RecordBatch::try_new(Arc::new(target_schema.clone()), columns)
184 .context(error::ArrowSnafu)?;
185 RecordBatchWithTsIdx::try_new(batch, timestamp_index)
186}
187
188pub(crate) fn identify_missing_columns_from_proto(
191 rows_schema: &[ColumnSchema],
192 target_schema: &ArrowSchema,
193) -> Result<Vec<String>> {
194 let (_, _, target_tags) = unzip_logical_region_schema(target_schema)?;
195 let mut missing = Vec::new();
196 for col in rows_schema {
197 let wrapper = ColumnDataTypeWrapper::try_new(col.datatype, col.datatype_extension.clone())?;
198 let arrow_type = ConcreteDataType::from(wrapper).as_arrow_type();
199 if matches!(arrow_type, ArrowDataType::Utf8)
200 && !target_tags.contains(&col.column_name)
201 && target_schema.column_with_name(&col.column_name).is_none()
202 {
203 missing.push(col.column_name.clone());
204 }
205 }
206 Ok(missing)
207}
208
209fn build_timestamp_array<T: ArrowPrimitiveType<Native = i64>>(
214 rows: &Rows,
215 col_idx: usize,
216 column_name: &str,
217 row_count: usize,
218 target_unit: CommonTimeUnit,
219) -> Result<ArrayRef> {
220 let mut builder = PrimitiveBuilder::<T>::with_capacity(row_count);
221 for row in &rows.rows {
222 let Some(value) = row.values[col_idx].value_data.as_ref() else {
223 builder.append_null();
224 continue;
225 };
226 let (source_unit, raw) = match value {
227 ValueData::TimestampSecondValue(v) => (CommonTimeUnit::Second, *v),
228 ValueData::TimestampMillisecondValue(v) => (CommonTimeUnit::Millisecond, *v),
229 ValueData::DatetimeValue(v) | ValueData::TimestampMicrosecondValue(v) => {
230 (CommonTimeUnit::Microsecond, *v)
231 }
232 ValueData::TimestampNanosecondValue(v) => (CommonTimeUnit::Nanosecond, *v),
233 v => {
234 return error::InvalidPromRemoteRequestSnafu {
235 msg: format!("Unexpected value: {:?}", v),
236 }
237 .fail();
238 }
239 };
240 if source_unit == target_unit {
241 builder.append_value(raw);
242 } else {
243 let timestamp = Timestamp::new(raw, source_unit);
244 let Some(converted) = timestamp.convert_to(target_unit) else {
245 return error::InvalidPromRemoteRequestSnafu {
246 msg: format!(
247 "Timestamp value in column '{column_name}' overflows when converting to unit {target_unit:?}"
248 ),
249 }
250 .fail();
251 };
252 builder.append_value(converted.value());
253 }
254 }
255 Ok(Arc::new(builder.finish()) as ArrayRef)
256}
257
258fn arrow_time_unit(unit: TimeUnit) -> CommonTimeUnit {
260 match unit {
261 TimeUnit::Second => CommonTimeUnit::Second,
262 TimeUnit::Millisecond => CommonTimeUnit::Millisecond,
263 TimeUnit::Microsecond => CommonTimeUnit::Microsecond,
264 TimeUnit::Nanosecond => CommonTimeUnit::Nanosecond,
265 }
266}
267
268pub fn build_prom_create_table_schema_from_proto(
271 rows_schema: &[ColumnSchema],
272) -> Result<Vec<ColumnSchema>> {
273 rows_schema
274 .iter()
275 .map(|col| {
276 let datatype = api::v1::ColumnDataType::try_from(col.datatype).map_err(|_| {
277 error::InvalidPromRemoteRequestSnafu {
278 msg: format!(
279 "Failed to build create table schema, column '{}' has unknown datatype {}",
280 col.column_name, col.datatype
281 ),
282 }
283 .build()
284 })?;
285 let semantic_type = if api::helper::timestamp_unit(datatype).is_some() {
286 SemanticType::Timestamp
287 } else if datatype == api::v1::ColumnDataType::Float64 {
288 SemanticType::Field
289 } else {
290 ensure!(datatype == api::v1::ColumnDataType::String, error::InvalidPromRemoteRequestSnafu{
292 msg: format!(
293 "Failed to build create table schema, tag column '{}' must be String but got datatype {}",
294 col.column_name, col.datatype
295 )
296 });
297 SemanticType::Tag
298 };
299
300 Ok(ColumnSchema {
301 column_name: col.column_name.clone(),
302 datatype: col.datatype,
303 semantic_type: semantic_type as i32,
304 datatype_extension: col.datatype_extension.clone(),
305 options: None,
306 })
307 })
308 .collect()
309}
310
311fn build_arrow_array(
313 rows: &Rows,
314 col_idx: usize,
315 column_name: &String,
316 column_data_type: arrow::datatypes::DataType,
317 row_count: usize,
318) -> Result<ArrayRef> {
319 macro_rules! build_array {
320 ($builder:expr, $( $pattern:pat => $value:expr ),+ $(,)?) => {{
321 let mut builder = $builder;
322 for row in &rows.rows {
323 match row.values[col_idx].value_data.as_ref() {
324 $(Some($pattern) => builder.append_value($value),)+
325 Some(v) => {
326 return error::InvalidPromRemoteRequestSnafu {
327 msg: format!("Unexpected value: {:?}", v),
328 }
329 .fail();
330 }
331 None => builder.append_null(),
332 }
333 }
334 Arc::new(builder.finish()) as ArrayRef
335 }};
336 }
337
338 let array: ArrayRef = match column_data_type {
339 arrow::datatypes::DataType::Float64 => {
340 build_array!(Float64Builder::with_capacity(row_count), ValueData::F64Value(v) => *v)
341 }
342 arrow::datatypes::DataType::Utf8 => build_array!(
343 StringBuilder::with_capacity(row_count, 0),
344 ValueData::StringValue(v) => v
345 ),
346 arrow::datatypes::DataType::Timestamp(u, _) => {
347 let target_unit = arrow_time_unit(u);
354 match u {
355 TimeUnit::Second => build_timestamp_array::<TimestampSecondType>(
356 rows,
357 col_idx,
358 column_name,
359 row_count,
360 target_unit,
361 )?,
362 TimeUnit::Millisecond => build_timestamp_array::<TimestampMillisecondType>(
363 rows,
364 col_idx,
365 column_name,
366 row_count,
367 target_unit,
368 )?,
369 TimeUnit::Microsecond => build_timestamp_array::<TimestampMicrosecondType>(
370 rows,
371 col_idx,
372 column_name,
373 row_count,
374 target_unit,
375 )?,
376 TimeUnit::Nanosecond => build_timestamp_array::<TimestampNanosecondType>(
377 rows,
378 col_idx,
379 column_name,
380 row_count,
381 target_unit,
382 )?,
383 }
384 }
385 ty => {
386 return error::InvalidPromRemoteRequestSnafu {
387 msg: format!(
388 "Unexpected column type {:?}, column name: {}",
389 ty, column_name
390 ),
391 }
392 .fail();
393 }
394 };
395
396 Ok(array)
397}
398
399#[cfg(test)]
400mod tests {
401 use api::v1::value::ValueData;
402 use api::v1::{ColumnDataType, ColumnSchema, Row, Rows, SemanticType, Value};
403 use arrow::array::{
404 Array, Float64Array, StringArray, TimestampMicrosecondArray, TimestampMillisecondArray,
405 };
406 use arrow::datatypes::{DataType, Field, Schema as ArrowSchema, TimeUnit};
407
408 use super::{
409 build_prom_create_table_schema_from_proto, identify_missing_columns_from_proto,
410 rows_to_aligned_record_batch,
411 };
412
413 #[test]
414 fn test_rows_to_aligned_record_batch_converts_time_units() {
415 let rows = Rows {
419 schema: vec![
420 ColumnSchema {
421 column_name: "greptime_timestamp".to_string(),
422 datatype: ColumnDataType::TimestampMicrosecond as i32,
423 semantic_type: SemanticType::Timestamp as i32,
424 ..Default::default()
425 },
426 ColumnSchema {
427 column_name: "greptime_value".to_string(),
428 datatype: ColumnDataType::Float64 as i32,
429 semantic_type: SemanticType::Field as i32,
430 ..Default::default()
431 },
432 ],
433 rows: vec![
434 Row {
435 values: vec![
436 Value {
437 value_data: Some(ValueData::TimestampMicrosecondValue(1000)),
438 },
439 Value {
440 value_data: Some(ValueData::F64Value(1.0)),
441 },
442 ],
443 },
444 Row {
445 values: vec![
446 Value {
447 value_data: Some(ValueData::TimestampMicrosecondValue(-1001)),
448 },
449 Value {
450 value_data: Some(ValueData::F64Value(2.0)),
451 },
452 ],
453 },
454 ],
455 };
456 let target = ArrowSchema::new(vec![
457 Field::new(
458 "greptime_timestamp",
459 DataType::Timestamp(TimeUnit::Millisecond, None),
460 false,
461 ),
462 Field::new("greptime_value", DataType::Float64, true),
463 ]);
464
465 let (batch, _) = rows_to_aligned_record_batch(&rows, &target)
466 .unwrap()
467 .into_parts();
468 let ts = batch
469 .column(0)
470 .as_any()
471 .downcast_ref::<TimestampMillisecondArray>()
472 .unwrap();
473 assert_eq!(ts.value(0), 1);
474 assert_eq!(ts.value(1), -2);
475
476 let mut rows = Rows {
479 schema: vec![rows.schema[0].clone(), rows.schema[1].clone()],
480 rows: vec![Row {
481 values: vec![
482 Value {
483 value_data: Some(ValueData::TimestampMillisecondValue(123)),
484 },
485 Value {
486 value_data: Some(ValueData::F64Value(4.0)),
487 },
488 ],
489 }],
490 };
491 rows.schema[0].datatype = ColumnDataType::TimestampMillisecond as i32;
492 let target = ArrowSchema::new(vec![
493 Field::new(
494 "greptime_timestamp",
495 DataType::Timestamp(TimeUnit::Microsecond, None),
496 false,
497 ),
498 Field::new("greptime_value", DataType::Float64, true),
499 ]);
500
501 let (batch, _) = rows_to_aligned_record_batch(&rows, &target)
502 .unwrap()
503 .into_parts();
504 let ts = batch
505 .column(0)
506 .as_any()
507 .downcast_ref::<TimestampMicrosecondArray>()
508 .unwrap();
509 assert_eq!(ts.value(0), 123_000);
510 }
511
512 #[test]
513 fn test_rows_to_aligned_record_batch_renames_and_reorders() {
514 let rows = Rows {
515 schema: vec![
516 ColumnSchema {
517 column_name: "greptime_timestamp".to_string(),
518 datatype: ColumnDataType::TimestampMillisecond as i32,
519 semantic_type: SemanticType::Timestamp as i32,
520 ..Default::default()
521 },
522 ColumnSchema {
523 column_name: "host".to_string(),
524 datatype: ColumnDataType::String as i32,
525 semantic_type: SemanticType::Tag as i32,
526 ..Default::default()
527 },
528 ColumnSchema {
529 column_name: "greptime_value".to_string(),
530 datatype: ColumnDataType::Float64 as i32,
531 semantic_type: SemanticType::Field as i32,
532 ..Default::default()
533 },
534 ],
535 rows: vec![
536 Row {
537 values: vec![
538 Value {
539 value_data: Some(ValueData::TimestampMillisecondValue(1000)),
540 },
541 Value {
542 value_data: Some(ValueData::StringValue("h1".to_string())),
543 },
544 Value {
545 value_data: Some(ValueData::F64Value(42.0)),
546 },
547 ],
548 },
549 Row {
550 values: vec![
551 Value {
552 value_data: Some(ValueData::TimestampMillisecondValue(2000)),
553 },
554 Value {
555 value_data: Some(ValueData::StringValue("h2".to_string())),
556 },
557 Value {
558 value_data: Some(ValueData::F64Value(99.0)),
559 },
560 ],
561 },
562 ],
563 };
564
565 let target = ArrowSchema::new(vec![
567 Field::new(
568 "my_ts",
569 DataType::Timestamp(TimeUnit::Millisecond, None),
570 false,
571 ),
572 Field::new("host", DataType::Utf8, true),
573 Field::new("my_value", DataType::Float64, true),
574 ]);
575
576 let aligned_batch = rows_to_aligned_record_batch(&rows, &target).unwrap();
577 let (batch, timestamp_index) = aligned_batch.into_parts();
578 assert_eq!(0, timestamp_index);
579 assert_eq!(batch.schema().as_ref(), &target);
580 assert_eq!(2, batch.num_rows());
581 assert_eq!(3, batch.num_columns());
582
583 let ts = batch
584 .column(0)
585 .as_any()
586 .downcast_ref::<TimestampMillisecondArray>()
587 .unwrap();
588 assert_eq!(ts.value(0), 1000);
589 assert_eq!(ts.value(1), 2000);
590
591 let hosts = batch
592 .column(1)
593 .as_any()
594 .downcast_ref::<StringArray>()
595 .unwrap();
596 assert_eq!(hosts.value(0), "h1");
597 assert_eq!(hosts.value(1), "h2");
598
599 let values = batch
600 .column(2)
601 .as_any()
602 .downcast_ref::<Float64Array>()
603 .unwrap();
604 assert_eq!(values.value(0), 42.0);
605 assert_eq!(values.value(1), 99.0);
606 }
607
608 #[test]
609 fn test_rows_to_aligned_record_batch_fills_nulls() {
610 let rows = Rows {
611 schema: vec![
612 ColumnSchema {
613 column_name: "greptime_timestamp".to_string(),
614 datatype: ColumnDataType::TimestampMillisecond as i32,
615 semantic_type: SemanticType::Timestamp as i32,
616 ..Default::default()
617 },
618 ColumnSchema {
619 column_name: "host".to_string(),
620 datatype: ColumnDataType::String as i32,
621 semantic_type: SemanticType::Tag as i32,
622 ..Default::default()
623 },
624 ColumnSchema {
625 column_name: "instance".to_string(),
626 datatype: ColumnDataType::String as i32,
627 semantic_type: SemanticType::Tag as i32,
628 ..Default::default()
629 },
630 ColumnSchema {
631 column_name: "greptime_value".to_string(),
632 datatype: ColumnDataType::Float64 as i32,
633 semantic_type: SemanticType::Field as i32,
634 ..Default::default()
635 },
636 ],
637 rows: vec![Row {
638 values: vec![
639 Value {
640 value_data: Some(ValueData::TimestampMillisecondValue(1000)),
641 },
642 Value {
643 value_data: Some(ValueData::StringValue("h1".to_string())),
644 },
645 Value {
646 value_data: Some(ValueData::StringValue("i1".to_string())),
647 },
648 Value {
649 value_data: Some(ValueData::F64Value(1.0)),
650 },
651 ],
652 }],
653 };
654
655 let target = ArrowSchema::new(vec![
657 Field::new(
658 "my_ts",
659 DataType::Timestamp(TimeUnit::Millisecond, None),
660 false,
661 ),
662 Field::new("host", DataType::Utf8, true),
663 Field::new("region", DataType::Utf8, true),
664 Field::new("my_value", DataType::Float64, true),
665 ]);
666
667 let aligned_batch = rows_to_aligned_record_batch(&rows, &target).unwrap();
668 let (batch, timestamp_index) = aligned_batch.into_parts();
669 assert_eq!(0, timestamp_index);
670 assert_eq!(batch.schema().as_ref(), &target);
671 assert_eq!(1, batch.num_rows());
672 assert_eq!(4, batch.num_columns());
673
674 let region = batch
676 .column(2)
677 .as_any()
678 .downcast_ref::<StringArray>()
679 .unwrap();
680 assert!(region.is_null(0));
681 }
682
683 #[test]
684 fn test_identify_missing_columns_from_proto() {
685 let rows_schema = vec![
686 ColumnSchema {
687 column_name: "greptime_timestamp".to_string(),
688 datatype: ColumnDataType::TimestampMillisecond as i32,
689 semantic_type: SemanticType::Timestamp as i32,
690 ..Default::default()
691 },
692 ColumnSchema {
693 column_name: "host".to_string(),
694 datatype: ColumnDataType::String as i32,
695 semantic_type: SemanticType::Tag as i32,
696 ..Default::default()
697 },
698 ColumnSchema {
699 column_name: "instance".to_string(),
700 datatype: ColumnDataType::String as i32,
701 semantic_type: SemanticType::Tag as i32,
702 ..Default::default()
703 },
704 ColumnSchema {
705 column_name: "greptime_value".to_string(),
706 datatype: ColumnDataType::Float64 as i32,
707 semantic_type: SemanticType::Field as i32,
708 ..Default::default()
709 },
710 ];
711
712 let target = ArrowSchema::new(vec![
713 Field::new(
714 "my_ts",
715 DataType::Timestamp(TimeUnit::Millisecond, None),
716 false,
717 ),
718 Field::new("host", DataType::Utf8, true),
719 Field::new("my_value", DataType::Float64, true),
720 ]);
721
722 let missing = identify_missing_columns_from_proto(&rows_schema, &target).unwrap();
723 assert_eq!(missing, vec!["instance".to_string()]);
724 }
725
726 #[test]
727 fn test_build_prom_create_table_schema_from_proto() {
728 let rows_schema = vec![
729 ColumnSchema {
730 column_name: "greptime_timestamp".to_string(),
731 datatype: ColumnDataType::TimestampMillisecond as i32,
732 semantic_type: SemanticType::Timestamp as i32,
733 ..Default::default()
734 },
735 ColumnSchema {
736 column_name: "job".to_string(),
737 datatype: ColumnDataType::String as i32,
738 semantic_type: SemanticType::Tag as i32,
739 ..Default::default()
740 },
741 ColumnSchema {
742 column_name: "greptime_value".to_string(),
743 datatype: ColumnDataType::Float64 as i32,
744 semantic_type: SemanticType::Field as i32,
745 ..Default::default()
746 },
747 ];
748
749 let schema = build_prom_create_table_schema_from_proto(&rows_schema).unwrap();
750 assert_eq!(3, schema.len());
751
752 assert_eq!("greptime_timestamp", schema[0].column_name);
753 assert_eq!(SemanticType::Timestamp as i32, schema[0].semantic_type);
754 assert_eq!(
755 ColumnDataType::TimestampMillisecond as i32,
756 schema[0].datatype
757 );
758
759 assert_eq!("job", schema[1].column_name);
760 assert_eq!(SemanticType::Tag as i32, schema[1].semantic_type);
761 assert_eq!(ColumnDataType::String as i32, schema[1].datatype);
762
763 assert_eq!("greptime_value", schema[2].column_name);
764 assert_eq!(SemanticType::Field as i32, schema[2].semantic_type);
765 assert_eq!(ColumnDataType::Float64 as i32, schema[2].datatype);
766 }
767}