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, Float64Builder, StringBuilder, TimestampMicrosecondBuilder,
27 TimestampMillisecondBuilder, TimestampNanosecondBuilder, TimestampSecondBuilder,
28 new_null_array,
29};
30use arrow::datatypes::{DataType as ArrowDataType, Schema as ArrowSchema};
31use arrow::record_batch::RecordBatch;
32use arrow_schema::TimeUnit;
33use common_query::prelude::{greptime_timestamp, greptime_value};
34use datatypes::data_type::DataType;
35use datatypes::prelude::ConcreteDataType;
36use snafu::{OptionExt, ResultExt, ensure};
37
38use crate::error;
39use crate::error::Result;
40use crate::pending_rows_batcher::RecordBatchWithTsIdx;
41
42fn unzip_logical_region_schema(
44 target_schema: &ArrowSchema,
45) -> Result<(String, String, HashSet<String>)> {
46 let mut timestamp_column = None;
47 let mut field_column = None;
48 let mut tag_columns = HashSet::with_capacity(target_schema.fields.len().saturating_sub(2));
49 for field in target_schema.fields() {
50 if field.name() == greptime_timestamp() {
51 timestamp_column = Some(field.name().clone());
52 continue;
53 }
54
55 if field.name() == greptime_value() {
56 field_column = Some(field.name().clone());
57 continue;
58 }
59
60 if timestamp_column.is_none() && matches!(field.data_type(), ArrowDataType::Timestamp(_, _))
61 {
62 timestamp_column = Some(field.name().clone());
63 continue;
64 }
65
66 if field_column.is_none() && matches!(field.data_type(), ArrowDataType::Float64) {
67 field_column = Some(field.name().clone());
68 continue;
69 }
70 tag_columns.insert(field.name().clone());
71 }
72
73 let timestamp_column = timestamp_column.with_context(|| error::UnexpectedResultSnafu {
74 reason: "Failed to locate timestamp column in target schema".to_string(),
75 })?;
76 let field_column = field_column.with_context(|| error::UnexpectedResultSnafu {
77 reason: "Failed to locate field column in target schema".to_string(),
78 })?;
79
80 Ok((timestamp_column, field_column, tag_columns))
81}
82
83pub(crate) fn rows_to_aligned_record_batch(
87 rows: &Rows,
88 target_schema: &ArrowSchema,
89) -> Result<RecordBatchWithTsIdx> {
90 let row_count = rows.rows.len();
91 let column_count = rows.schema.len();
92
93 for (idx, row) in rows.rows.iter().enumerate() {
94 ensure!(
95 row.values.len() == column_count,
96 error::InternalSnafu {
97 err_msg: format!(
98 "Column count mismatch in row {}, expected {}, got {}",
99 idx,
100 column_count,
101 row.values.len()
102 )
103 }
104 );
105 }
106
107 let (target_ts_name, target_field_name, _target_tags) =
108 unzip_logical_region_schema(target_schema)?;
109 let timestamp_index = target_schema
110 .column_with_name(&target_ts_name)
111 .map(|(index, _)| index)
112 .with_context(|| error::UnexpectedResultSnafu {
113 reason: format!(
114 "Failed to resolve timestamp column '{}' in target schema",
115 target_ts_name
116 ),
117 })?;
118
119 let mut source_map: HashMap<&str, (usize, ArrowDataType)> =
122 HashMap::with_capacity(rows.schema.len());
123
124 for (src_idx, col) in rows.schema.iter().enumerate() {
125 let wrapper = ColumnDataTypeWrapper::try_new(col.datatype, col.datatype_extension.clone())?;
126 let src_arrow_type = ConcreteDataType::from(wrapper).as_arrow_type();
127
128 match &src_arrow_type {
129 ArrowDataType::Float64 => {
130 source_map.insert(&target_field_name, (src_idx, src_arrow_type));
131 }
132 ArrowDataType::Timestamp(unit, _) => {
133 ensure!(
134 unit == &TimeUnit::Millisecond,
135 error::InvalidPromRemoteRequestSnafu {
136 msg: format!(
137 "Unexpected remote write batch timestamp unit, expect millisecond, got: {}",
138 unit
139 )
140 }
141 );
142 source_map.insert(&target_ts_name, (src_idx, src_arrow_type));
143 }
144 ArrowDataType::Utf8 => {
145 source_map.insert(&col.column_name, (src_idx, src_arrow_type));
146 }
147 other => {
148 return error::InvalidPromRemoteRequestSnafu {
149 msg: format!(
150 "Unexpected remote write batch field type {}, field name: {}",
151 other, col.column_name
152 ),
153 }
154 .fail();
155 }
156 }
157 }
158
159 let mut columns = Vec::with_capacity(target_schema.fields().len());
161 for target_field in target_schema.fields() {
162 if let Some((src_idx, src_arrow_type)) = source_map.get(target_field.name().as_str()) {
163 let array = build_arrow_array(
164 rows,
165 *src_idx,
166 &rows.schema[*src_idx].column_name,
167 src_arrow_type.clone(),
168 row_count,
169 )?;
170 columns.push(array);
171 } else {
172 columns.push(new_null_array(target_field.data_type(), row_count));
173 }
174 }
175
176 let batch = RecordBatch::try_new(Arc::new(target_schema.clone()), columns)
177 .context(error::ArrowSnafu)?;
178 RecordBatchWithTsIdx::try_new(batch, timestamp_index)
179}
180
181pub(crate) fn identify_missing_columns_from_proto(
184 rows_schema: &[ColumnSchema],
185 target_schema: &ArrowSchema,
186) -> Result<Vec<String>> {
187 let (_, _, target_tags) = unzip_logical_region_schema(target_schema)?;
188 let mut missing = Vec::new();
189 for col in rows_schema {
190 let wrapper = ColumnDataTypeWrapper::try_new(col.datatype, col.datatype_extension.clone())?;
191 let arrow_type = ConcreteDataType::from(wrapper).as_arrow_type();
192 if matches!(arrow_type, ArrowDataType::Utf8)
193 && !target_tags.contains(&col.column_name)
194 && target_schema.column_with_name(&col.column_name).is_none()
195 {
196 missing.push(col.column_name.clone());
197 }
198 }
199 Ok(missing)
200}
201
202pub fn build_prom_create_table_schema_from_proto(
205 rows_schema: &[ColumnSchema],
206) -> Result<Vec<ColumnSchema>> {
207 rows_schema
208 .iter()
209 .map(|col| {
210 let semantic_type = if col.datatype == api::v1::ColumnDataType::TimestampMillisecond as i32 {
211 SemanticType::Timestamp
212 } else if col.datatype == api::v1::ColumnDataType::Float64 as i32 {
213 SemanticType::Field
214 } else {
215 ensure!(col.datatype == api::v1::ColumnDataType::String as i32, error::InvalidPromRemoteRequestSnafu{
217 msg: format!(
218 "Failed to build create table schema, tag column '{}' must be String but got datatype {}",
219 col.column_name, col.datatype
220 )
221 });
222 SemanticType::Tag
223 };
224
225 Ok(ColumnSchema {
226 column_name: col.column_name.clone(),
227 datatype: col.datatype,
228 semantic_type: semantic_type as i32,
229 datatype_extension: col.datatype_extension.clone(),
230 options: None,
231 })
232 })
233 .collect()
234}
235
236fn build_arrow_array(
238 rows: &Rows,
239 col_idx: usize,
240 column_name: &String,
241 column_data_type: arrow::datatypes::DataType,
242 row_count: usize,
243) -> Result<ArrayRef> {
244 macro_rules! build_array {
245 ($builder:expr, $( $pattern:pat => $value:expr ),+ $(,)?) => {{
246 let mut builder = $builder;
247 for row in &rows.rows {
248 match row.values[col_idx].value_data.as_ref() {
249 $(Some($pattern) => builder.append_value($value),)+
250 Some(v) => {
251 return error::InvalidPromRemoteRequestSnafu {
252 msg: format!("Unexpected value: {:?}", v),
253 }
254 .fail();
255 }
256 None => builder.append_null(),
257 }
258 }
259 Arc::new(builder.finish()) as ArrayRef
260 }};
261 }
262
263 let array: ArrayRef = match column_data_type {
264 arrow::datatypes::DataType::Float64 => {
265 build_array!(Float64Builder::with_capacity(row_count), ValueData::F64Value(v) => *v)
266 }
267 arrow::datatypes::DataType::Utf8 => build_array!(
268 StringBuilder::with_capacity(row_count, 0),
269 ValueData::StringValue(v) => v
270 ),
271 arrow::datatypes::DataType::Timestamp(u, _) => match u {
272 TimeUnit::Second => build_array!(
273 TimestampSecondBuilder::with_capacity(row_count),
274 ValueData::TimestampSecondValue(v) => *v
275 ),
276 TimeUnit::Millisecond => build_array!(
277 TimestampMillisecondBuilder::with_capacity(row_count),
278 ValueData::TimestampMillisecondValue(v) => *v
279 ),
280 TimeUnit::Microsecond => build_array!(
281 TimestampMicrosecondBuilder::with_capacity(row_count),
282 ValueData::DatetimeValue(v) => *v,
283 ValueData::TimestampMicrosecondValue(v) => *v
284 ),
285 TimeUnit::Nanosecond => build_array!(
286 TimestampNanosecondBuilder::with_capacity(row_count),
287 ValueData::TimestampNanosecondValue(v) => *v
288 ),
289 },
290 ty => {
291 return error::InvalidPromRemoteRequestSnafu {
292 msg: format!(
293 "Unexpected column type {:?}, column name: {}",
294 ty, column_name
295 ),
296 }
297 .fail();
298 }
299 };
300
301 Ok(array)
302}
303
304#[cfg(test)]
305mod tests {
306 use api::v1::value::ValueData;
307 use api::v1::{ColumnDataType, ColumnSchema, Row, Rows, SemanticType, Value};
308 use arrow::array::{Array, Float64Array, StringArray, TimestampMillisecondArray};
309 use arrow::datatypes::{DataType, Field, Schema as ArrowSchema, TimeUnit};
310
311 use super::{
312 build_prom_create_table_schema_from_proto, identify_missing_columns_from_proto,
313 rows_to_aligned_record_batch,
314 };
315
316 #[test]
317 fn test_rows_to_aligned_record_batch_renames_and_reorders() {
318 let rows = Rows {
319 schema: vec![
320 ColumnSchema {
321 column_name: "greptime_timestamp".to_string(),
322 datatype: ColumnDataType::TimestampMillisecond as i32,
323 semantic_type: SemanticType::Timestamp as i32,
324 ..Default::default()
325 },
326 ColumnSchema {
327 column_name: "host".to_string(),
328 datatype: ColumnDataType::String as i32,
329 semantic_type: SemanticType::Tag as i32,
330 ..Default::default()
331 },
332 ColumnSchema {
333 column_name: "greptime_value".to_string(),
334 datatype: ColumnDataType::Float64 as i32,
335 semantic_type: SemanticType::Field as i32,
336 ..Default::default()
337 },
338 ],
339 rows: vec![
340 Row {
341 values: vec![
342 Value {
343 value_data: Some(ValueData::TimestampMillisecondValue(1000)),
344 },
345 Value {
346 value_data: Some(ValueData::StringValue("h1".to_string())),
347 },
348 Value {
349 value_data: Some(ValueData::F64Value(42.0)),
350 },
351 ],
352 },
353 Row {
354 values: vec![
355 Value {
356 value_data: Some(ValueData::TimestampMillisecondValue(2000)),
357 },
358 Value {
359 value_data: Some(ValueData::StringValue("h2".to_string())),
360 },
361 Value {
362 value_data: Some(ValueData::F64Value(99.0)),
363 },
364 ],
365 },
366 ],
367 };
368
369 let target = ArrowSchema::new(vec![
371 Field::new(
372 "my_ts",
373 DataType::Timestamp(TimeUnit::Millisecond, None),
374 false,
375 ),
376 Field::new("host", DataType::Utf8, true),
377 Field::new("my_value", DataType::Float64, true),
378 ]);
379
380 let aligned_batch = rows_to_aligned_record_batch(&rows, &target).unwrap();
381 let (batch, timestamp_index) = aligned_batch.into_parts();
382 assert_eq!(0, timestamp_index);
383 assert_eq!(batch.schema().as_ref(), &target);
384 assert_eq!(2, batch.num_rows());
385 assert_eq!(3, batch.num_columns());
386
387 let ts = batch
388 .column(0)
389 .as_any()
390 .downcast_ref::<TimestampMillisecondArray>()
391 .unwrap();
392 assert_eq!(ts.value(0), 1000);
393 assert_eq!(ts.value(1), 2000);
394
395 let hosts = batch
396 .column(1)
397 .as_any()
398 .downcast_ref::<StringArray>()
399 .unwrap();
400 assert_eq!(hosts.value(0), "h1");
401 assert_eq!(hosts.value(1), "h2");
402
403 let values = batch
404 .column(2)
405 .as_any()
406 .downcast_ref::<Float64Array>()
407 .unwrap();
408 assert_eq!(values.value(0), 42.0);
409 assert_eq!(values.value(1), 99.0);
410 }
411
412 #[test]
413 fn test_rows_to_aligned_record_batch_fills_nulls() {
414 let rows = Rows {
415 schema: vec![
416 ColumnSchema {
417 column_name: "greptime_timestamp".to_string(),
418 datatype: ColumnDataType::TimestampMillisecond as i32,
419 semantic_type: SemanticType::Timestamp as i32,
420 ..Default::default()
421 },
422 ColumnSchema {
423 column_name: "host".to_string(),
424 datatype: ColumnDataType::String as i32,
425 semantic_type: SemanticType::Tag as i32,
426 ..Default::default()
427 },
428 ColumnSchema {
429 column_name: "instance".to_string(),
430 datatype: ColumnDataType::String as i32,
431 semantic_type: SemanticType::Tag as i32,
432 ..Default::default()
433 },
434 ColumnSchema {
435 column_name: "greptime_value".to_string(),
436 datatype: ColumnDataType::Float64 as i32,
437 semantic_type: SemanticType::Field as i32,
438 ..Default::default()
439 },
440 ],
441 rows: vec![Row {
442 values: vec![
443 Value {
444 value_data: Some(ValueData::TimestampMillisecondValue(1000)),
445 },
446 Value {
447 value_data: Some(ValueData::StringValue("h1".to_string())),
448 },
449 Value {
450 value_data: Some(ValueData::StringValue("i1".to_string())),
451 },
452 Value {
453 value_data: Some(ValueData::F64Value(1.0)),
454 },
455 ],
456 }],
457 };
458
459 let target = ArrowSchema::new(vec![
461 Field::new(
462 "my_ts",
463 DataType::Timestamp(TimeUnit::Millisecond, None),
464 false,
465 ),
466 Field::new("host", DataType::Utf8, true),
467 Field::new("region", DataType::Utf8, true),
468 Field::new("my_value", DataType::Float64, true),
469 ]);
470
471 let aligned_batch = rows_to_aligned_record_batch(&rows, &target).unwrap();
472 let (batch, timestamp_index) = aligned_batch.into_parts();
473 assert_eq!(0, timestamp_index);
474 assert_eq!(batch.schema().as_ref(), &target);
475 assert_eq!(1, batch.num_rows());
476 assert_eq!(4, batch.num_columns());
477
478 let region = batch
480 .column(2)
481 .as_any()
482 .downcast_ref::<StringArray>()
483 .unwrap();
484 assert!(region.is_null(0));
485 }
486
487 #[test]
488 fn test_identify_missing_columns_from_proto() {
489 let rows_schema = vec![
490 ColumnSchema {
491 column_name: "greptime_timestamp".to_string(),
492 datatype: ColumnDataType::TimestampMillisecond as i32,
493 semantic_type: SemanticType::Timestamp as i32,
494 ..Default::default()
495 },
496 ColumnSchema {
497 column_name: "host".to_string(),
498 datatype: ColumnDataType::String as i32,
499 semantic_type: SemanticType::Tag as i32,
500 ..Default::default()
501 },
502 ColumnSchema {
503 column_name: "instance".to_string(),
504 datatype: ColumnDataType::String as i32,
505 semantic_type: SemanticType::Tag as i32,
506 ..Default::default()
507 },
508 ColumnSchema {
509 column_name: "greptime_value".to_string(),
510 datatype: ColumnDataType::Float64 as i32,
511 semantic_type: SemanticType::Field as i32,
512 ..Default::default()
513 },
514 ];
515
516 let target = ArrowSchema::new(vec![
517 Field::new(
518 "my_ts",
519 DataType::Timestamp(TimeUnit::Millisecond, None),
520 false,
521 ),
522 Field::new("host", DataType::Utf8, true),
523 Field::new("my_value", DataType::Float64, true),
524 ]);
525
526 let missing = identify_missing_columns_from_proto(&rows_schema, &target).unwrap();
527 assert_eq!(missing, vec!["instance".to_string()]);
528 }
529
530 #[test]
531 fn test_build_prom_create_table_schema_from_proto() {
532 let rows_schema = vec![
533 ColumnSchema {
534 column_name: "greptime_timestamp".to_string(),
535 datatype: ColumnDataType::TimestampMillisecond as i32,
536 semantic_type: SemanticType::Timestamp as i32,
537 ..Default::default()
538 },
539 ColumnSchema {
540 column_name: "job".to_string(),
541 datatype: ColumnDataType::String as i32,
542 semantic_type: SemanticType::Tag as i32,
543 ..Default::default()
544 },
545 ColumnSchema {
546 column_name: "greptime_value".to_string(),
547 datatype: ColumnDataType::Float64 as i32,
548 semantic_type: SemanticType::Field as i32,
549 ..Default::default()
550 },
551 ];
552
553 let schema = build_prom_create_table_schema_from_proto(&rows_schema).unwrap();
554 assert_eq!(3, schema.len());
555
556 assert_eq!("greptime_timestamp", schema[0].column_name);
557 assert_eq!(SemanticType::Timestamp as i32, schema[0].semantic_type);
558 assert_eq!(
559 ColumnDataType::TimestampMillisecond as i32,
560 schema[0].datatype
561 );
562
563 assert_eq!("job", schema[1].column_name);
564 assert_eq!(SemanticType::Tag as i32, schema[1].semantic_type);
565 assert_eq!(ColumnDataType::String as i32, schema[1].datatype);
566
567 assert_eq!("greptime_value", schema[2].column_name);
568 assert_eq!(SemanticType::Field as i32, schema[2].semantic_type);
569 assert_eq!(ColumnDataType::Float64 as i32, schema[2].datatype);
570 }
571}