1use std::collections::HashMap;
16
17use api::helper::{
18 ColumnDataTypeWrapper, pb_value_to_value_ref, proto_value_type, proto_value_type_match,
19};
20use api::v1::column_data_type_extension::TypeExt;
21use api::v1::value::ValueData;
22use api::v1::{ColumnDataType, ColumnDataTypeExtension, Rows, SemanticType, Value};
23use arrow::record_batch::RecordBatch;
24use common_error::ext::BoxedError;
25use datatypes::data_type::ConcreteDataType;
26use datatypes::extension::json::align_schema_with_json_array;
27use snafu::{OptionExt, ResultExt, ensure};
28use table::metadata::TableInfo;
29
30use crate::error::{self, Result};
31
32pub fn rows_to_record_batch(rows: &Rows, table_info: &TableInfo) -> Result<RecordBatch> {
42 assert!(!rows.rows.is_empty(), "prepared rows must not be empty");
43 let schema = &table_info.meta.schema;
44 let mut source_columns = HashMap::with_capacity(rows.schema.len());
45 for (index, source) in rows.schema.iter().enumerate() {
46 ensure!(
47 source_columns
48 .insert(source.column_name.as_str(), index)
49 .is_none(),
50 error::InvalidInsertRequestSnafu {
51 reason: format!("Duplicate input column {}", source.column_name),
52 }
53 );
54 let target = schema
55 .column_schema_by_name(&source.column_name)
56 .with_context(|| error::InvalidInsertRequestSnafu {
57 reason: format!("Unknown input column {}", source.column_name),
58 })?;
59 let data_type =
60 ColumnDataTypeWrapper::try_new(source.datatype, source.datatype_extension.clone())
61 .map_err(BoxedError::new)
62 .context(error::ExternalSnafu)?;
63 ensure!(
64 ConcreteDataType::from(data_type) == target.data_type,
65 error::InvalidInsertRequestSnafu {
66 reason: format!("Input datatype differs for column {}", source.column_name),
67 }
68 );
69 let semantic = SemanticType::try_from(source.semantic_type)
70 .ok()
71 .with_context(|| error::InvalidInsertRequestSnafu {
72 reason: format!("Invalid semantic type for column {}", source.column_name),
73 })?;
74 let is_tag = table_info.meta.primary_key_indices.iter().any(|&index| {
75 schema
76 .column_schemas()
77 .get(index)
78 .is_some_and(|column| column.name == source.column_name)
79 });
80 let expected_semantic = if target.is_time_index() {
81 SemanticType::Timestamp
82 } else if is_tag {
83 SemanticType::Tag
84 } else {
85 SemanticType::Field
86 };
87 ensure!(
88 semantic == expected_semantic,
89 error::InvalidInsertRequestSnafu {
90 reason: format!("Input semantics differ for column {}", source.column_name),
91 }
92 );
93 }
94 for row in &rows.rows {
95 ensure!(
96 row.values.len() == rows.schema.len(),
97 error::InvalidInsertRequestSnafu {
98 reason: format!(
99 "Expected {} values, got {}",
100 rows.schema.len(),
101 row.values.len()
102 ),
103 }
104 );
105 }
106
107 let mut arrays = Vec::with_capacity(schema.num_columns());
108 for column in schema.column_schemas() {
109 let vector = if let Some(&index) = source_columns.get(column.name.as_str()) {
110 let mut builder = column.create_mutable_vector(rows.rows.len());
111 for row in &rows.rows {
112 let value = &row.values[index];
113 ensure!(
114 value.value_data.is_some() || column.is_nullable(),
115 error::InvalidInsertRequestSnafu {
116 reason: format!("Null supplied for non-nullable column {}", column.name),
117 }
118 );
119 ensure!(
120 value_matches_type(
121 value,
122 rows.schema[index].datatype,
123 rows.schema[index].datatype_extension.as_ref(),
124 ),
125 error::InvalidInsertRequestSnafu {
126 reason: format!("Value datatype differs for column {}", column.name),
127 }
128 );
129 builder
130 .try_push_value_ref(&pb_value_to_value_ref(
131 value,
132 rows.schema[index].datatype_extension.as_ref(),
133 ))
134 .map_err(BoxedError::new)
135 .context(error::ExternalSnafu)?;
136 }
137 builder.to_vector()
138 } else {
139 column
140 .create_default_vector(rows.rows.len())
141 .map_err(BoxedError::new)
142 .context(error::ExternalSnafu)?
143 .with_context(|| error::InvalidInsertRequestSnafu {
144 reason: format!("Missing required column {}", column.name),
145 })?
146 };
147 arrays.push(vector.to_arrow_array());
148 }
149 let arrow_schema = align_schema_with_json_array(schema.arrow_schema().clone(), &arrays);
150 RecordBatch::try_new(arrow_schema, arrays).context(error::ComputeArrowSnafu)
151}
152
153fn value_matches_type(
155 value: &Value,
156 datatype: i32,
157 extension: Option<&ColumnDataTypeExtension>,
158) -> bool {
159 let Some(value_type) = proto_value_type(value) else {
160 return true;
161 };
162 let Ok(column_type) = ColumnDataType::try_from(datatype) else {
163 return false;
164 };
165 if !proto_value_type_match(column_type, value_type) {
166 return false;
167 }
168 match value.value_data.as_ref() {
169 Some(ValueData::ListValue(list)) => {
170 let Some(TypeExt::ListType(item)) = extension.and_then(|ext| ext.type_ext.as_ref())
171 else {
172 return false;
173 };
174 list.items.iter().all(|value| {
175 value_matches_type(value, item.datatype, item.datatype_extension.as_deref())
176 })
177 }
178 Some(ValueData::StructValue(value)) => {
179 let Some(TypeExt::StructType(schema)) = extension.and_then(|ext| ext.type_ext.as_ref())
180 else {
181 return false;
182 };
183 value.items.len() == schema.fields.len()
184 && value
185 .items
186 .iter()
187 .zip(&schema.fields)
188 .all(|(value, field)| {
189 value_matches_type(value, field.datatype, field.datatype_extension.as_ref())
190 })
191 }
192 _ => true,
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use std::sync::Arc;
199 use std::thread::yield_now;
200
201 use api::v1::value::ValueData;
202 use api::v1::{ColumnDataType, ColumnSchema as ProtoColumnSchema, Row, Value};
203 use arrow::array::{Array, Int32Array, StringArray};
204 use datatypes::schema::{ColumnDefaultConstraint, ColumnSchema, Schema};
205 use datatypes::value::Value as DtValue;
206 use table::metadata::{TableInfoBuilder, TableMetaBuilder};
207
208 use crate::req_convert::insert::row_to_batch::*;
209
210 fn source(name: &str, datatype: ColumnDataType) -> ProtoColumnSchema {
211 ProtoColumnSchema {
212 column_name: name.to_string(),
213 datatype: datatype as i32,
214 semantic_type: SemanticType::Field as i32,
215 ..Default::default()
216 }
217 }
218
219 fn table_info(schema: Schema) -> TableInfo {
220 let next_column_id = schema.num_columns() as u32;
221 TableInfoBuilder::default()
222 .table_id(1)
223 .table_version(0)
224 .name("test")
225 .meta(
226 TableMetaBuilder::empty()
227 .schema(Arc::new(schema))
228 .primary_key_indices(vec![])
229 .next_column_id(next_column_id)
230 .engine("mito")
231 .build()
232 .unwrap(),
233 )
234 .build()
235 .unwrap()
236 }
237
238 fn fixture() -> (Rows, Schema) {
239 let schema = Schema::new(vec![
240 ColumnSchema::new("count", ConcreteDataType::int32_datatype(), false),
241 ColumnSchema::new("label", ConcreteDataType::string_datatype(), true),
242 ColumnSchema::new("fallback", ConcreteDataType::int32_datatype(), true)
243 .with_default_constraint(Some(ColumnDefaultConstraint::Value(DtValue::Int32(7))))
244 .unwrap(),
245 ]);
246 let rows = Rows {
247 schema: vec![
248 source("label", ColumnDataType::String),
249 source("count", ColumnDataType::Int32),
250 ],
251 rows: vec![Row {
252 values: vec![
253 Value::default(),
254 Value {
255 value_data: Some(ValueData::I32Value(3)),
256 },
257 ],
258 }],
259 };
260 (rows, schema)
261 }
262
263 #[test]
264 fn test_reorder_multifield_and_defaults() {
265 let (rows, schema) = fixture();
266 let batch = rows_to_record_batch(&rows, &table_info(schema.clone())).unwrap();
267 assert_eq!(batch.schema(), schema.arrow_schema().clone());
268 assert_eq!(
269 batch
270 .column(0)
271 .as_any()
272 .downcast_ref::<Int32Array>()
273 .unwrap()
274 .value(0),
275 3
276 );
277 assert!(
278 batch
279 .column(1)
280 .as_any()
281 .downcast_ref::<StringArray>()
282 .unwrap()
283 .is_null(0)
284 );
285 assert_eq!(
286 batch
287 .column(2)
288 .as_any()
289 .downcast_ref::<Int32Array>()
290 .unwrap()
291 .value(0),
292 7
293 );
294 }
295
296 #[test]
297 fn test_explicit_null_does_not_use_default() {
298 let (mut rows, schema) = fixture();
299 rows.schema.push(source("fallback", ColumnDataType::Int32));
300 rows.rows[0].values.push(Value::default());
301 assert!(
302 rows_to_record_batch(&rows, &table_info(schema.clone()))
303 .unwrap()
304 .column(2)
305 .is_null(0)
306 );
307 }
308
309 #[test]
310 fn test_timestamp_precisions() {
311 for (datatype, value) in [
312 (
313 ColumnDataType::TimestampSecond,
314 ValueData::TimestampSecondValue(42),
315 ),
316 (
317 ColumnDataType::TimestampMillisecond,
318 ValueData::TimestampMillisecondValue(42),
319 ),
320 (
321 ColumnDataType::TimestampMicrosecond,
322 ValueData::TimestampMicrosecondValue(42),
323 ),
324 (
325 ColumnDataType::TimestampNanosecond,
326 ValueData::TimestampNanosecondValue(42),
327 ),
328 ] {
329 let target_type = ConcreteDataType::from(ColumnDataTypeWrapper::new(datatype, None));
330 let schema = Schema::new(vec![
331 ColumnSchema::new("ts", target_type, false).with_time_index(true),
332 ]);
333 let mut column = source("ts", datatype);
334 column.semantic_type = SemanticType::Timestamp as i32;
335 let rows = Rows {
336 schema: vec![column],
337 rows: vec![Row {
338 values: vec![Value {
339 value_data: Some(value),
340 }],
341 }],
342 };
343 let batch = rows_to_record_batch(&rows, &table_info(schema.clone())).unwrap();
344 assert_eq!(batch.schema(), schema.arrow_schema().clone());
345 assert_eq!(batch.num_rows(), 1);
346 assert_eq!(batch.column(0).null_count(), 0);
347 }
348 }
349
350 #[test]
351 fn test_json2_expanded_schema() {
352 use api::helper::to_grpc_value;
353 use datatypes::extension::json::{Json2ExtensionType, JsonMetadata};
354 use datatypes::json::JsonSettings;
355 use datatypes::schema::SchemaBuilder;
356 use datatypes::types::json_type::JsonNativeType;
357
358 let settings = JsonSettings::default();
359 let mut column_schema = ColumnSchema::new(
360 "data",
361 ConcreteDataType::json2(JsonNativeType::object()),
362 true,
363 );
364 column_schema.with_extension_type(&Json2ExtensionType::new(Arc::new(JsonMetadata::new(
365 settings.clone(),
366 ))));
367 let schema = SchemaBuilder::try_from(vec![column_schema])
368 .unwrap()
369 .add_metadata("test", "metadata")
370 .build()
371 .unwrap();
372 let arrow_schema = schema.arrow_schema().clone();
373 let field = arrow_schema.field(0);
374 let datatype =
375 ColumnDataTypeWrapper::try_from(schema.column_schemas()[0].data_type.clone()).unwrap();
376 let (kind, extension) = datatype.to_parts();
377 let mut column = source("data", kind);
378 column.datatype_extension = extension;
379 let rows = Rows {
380 schema: vec![column],
381 rows: vec![Row {
382 values: vec![to_grpc_value(
383 settings.encode(serde_json::json!({"id": 3})).unwrap(),
384 )],
385 }],
386 };
387 let batch = rows_to_record_batch(&rows, &table_info(schema)).unwrap();
388 assert!(RecordBatch::try_new(arrow_schema.clone(), batch.columns().to_vec()).is_err());
390 let actual_schema = batch.schema();
391 assert_eq!(actual_schema.metadata(), arrow_schema.metadata());
392 assert_eq!(actual_schema.field(0).metadata(), field.metadata());
393 assert_eq!(actual_schema.field(0).name(), field.name());
394 assert_eq!(actual_schema.field(0).is_nullable(), field.is_nullable());
395 assert_eq!(
396 actual_schema.field(0).data_type(),
397 batch.column(0).data_type()
398 );
399 let array = batch
400 .column(0)
401 .as_any()
402 .downcast_ref::<arrow::array::StructArray>()
403 .unwrap();
404 assert!(array.column_by_name("id").is_some());
405 assert!(array.column_by_name("!__remainder__!").is_some());
406 assert_eq!(batch.num_rows(), 1);
407 }
408
409 #[test]
410 fn test_dynamic_default_is_evaluated_per_conversion() {
411 use std::time::{Duration, Instant};
412
413 use arrow::array::TimestampMillisecondArray;
414
415 let (mut rows, base_schema) = fixture();
416 let mut columns = base_schema.column_schemas().to_vec();
417 columns.push(
418 ColumnSchema::new(
419 "created",
420 ConcreteDataType::timestamp_millisecond_datatype(),
421 true,
422 )
423 .with_default_constraint(Some(ColumnDefaultConstraint::Function(
424 "current_timestamp()".to_string(),
425 )))
426 .unwrap(),
427 );
428 let schema = Schema::new(columns);
429 let timestamp = |batch: &RecordBatch| {
430 batch
431 .column(3)
432 .as_any()
433 .downcast_ref::<TimestampMillisecondArray>()
434 .unwrap()
435 .value(0)
436 };
437 let first = timestamp(&rows_to_record_batch(&rows, &table_info(schema.clone())).unwrap());
438 let deadline = Instant::now() + Duration::from_secs(1);
441 loop {
442 let next =
443 timestamp(&rows_to_record_batch(&rows, &table_info(schema.clone())).unwrap());
444 if next != first {
445 break;
446 }
447 assert!(
448 Instant::now() < deadline,
449 "dynamic default was not refreshed"
450 );
451 yield_now();
452 }
453 rows.schema
454 .push(source("created", ColumnDataType::TimestampMillisecond));
455 rows.rows[0].values.push(Value::default());
456 assert!(
457 rows_to_record_batch(&rows, &table_info(schema.clone()))
458 .unwrap()
459 .column(3)
460 .is_null(0)
461 );
462 }
463
464 #[test]
465 fn test_tag_and_field_semantics() {
466 let (mut rows, schema) = fixture();
467 let mut table = table_info(schema);
468 table.meta.primary_key_indices = vec![1];
469 assert!(rows_to_record_batch(&rows, &table).is_err());
471 rows.schema[0].semantic_type = SemanticType::Tag as i32;
472 assert!(rows_to_record_batch(&rows, &table).is_ok());
473 rows.schema[1].semantic_type = SemanticType::Tag as i32;
475 assert!(rows_to_record_batch(&rows, &table).is_err());
476 }
477
478 #[test]
479 fn test_invalid_input() {
480 for case in 0..7 {
481 let (mut rows, schema) = fixture();
482 match case {
483 0 => rows.schema[0].column_name = "unknown".to_string(),
484 1 => rows.schema[0].column_name = "count".to_string(),
485 2 => {
486 rows.rows[0].values.pop();
487 }
488 3 => rows.schema[1].datatype = ColumnDataType::Float64 as i32,
489 4 => rows.rows[0].values[1] = Value::default(),
490 5 => rows.schema[1].semantic_type = SemanticType::Timestamp as i32,
491 _ => {
492 rows.rows[0].values[1].value_data =
493 Some(ValueData::StringValue("wrong".to_string()))
494 }
495 }
496 assert!(
497 rows_to_record_batch(&rows, &table_info(schema.clone())).is_err(),
498 "case {case}"
499 );
500 }
501 }
502
503 #[test]
504 #[should_panic(expected = "prepared rows must not be empty")]
505 fn test_empty_rows_contract() {
506 let (mut rows, schema) = fixture();
507 rows.rows.clear();
508 let _ = rows_to_record_batch(&rows, &table_info(schema));
509 }
510
511 #[test]
512 fn test_reject_nested_values_for_scalar_column() {
513 for value in [
514 ValueData::ListValue(api::v1::ListValue { items: vec![] }),
515 ValueData::StructValue(api::v1::StructValue { items: vec![] }),
516 ] {
517 let (mut rows, schema) = fixture();
518 rows.rows[0].values[1].value_data = Some(value);
519 assert!(matches!(
520 rows_to_record_batch(&rows, &table_info(schema)),
521 Err(error::Error::InvalidInsertRequest { .. })
522 ));
523 }
524 }
525
526 #[test]
527 fn test_nested_value_validation() {
528 let int = Value {
529 value_data: Some(ValueData::I32Value(1)),
530 };
531 let list = Value {
532 value_data: Some(ValueData::ListValue(api::v1::ListValue {
533 items: vec![int.clone()],
534 })),
535 };
536 let structure = Value {
537 value_data: Some(ValueData::StructValue(api::v1::StructValue {
538 items: vec![int.clone()],
539 })),
540 };
541 let types = [
542 (
543 ColumnDataTypeWrapper::list_datatype(ColumnDataTypeWrapper::int32_datatype()),
544 list.clone(),
545 ),
546 (
547 ColumnDataTypeWrapper::struct_datatype(vec![(
548 "count".to_string(),
549 ColumnDataTypeWrapper::int32_datatype(),
550 )]),
551 structure.clone(),
552 ),
553 ];
554 for (datatype, valid) in types {
555 let (kind, extension) = datatype.to_parts();
556 let schema = Schema::new(vec![ColumnSchema::new("nested", datatype.into(), true)]);
557 let mut column = source("nested", kind);
558 column.datatype_extension = extension;
559 let mut rows = Rows {
560 schema: vec![column],
561 rows: vec![Row {
562 values: vec![valid],
563 }],
564 };
565 let table = table_info(schema);
566 assert_eq!(rows_to_record_batch(&rows, &table).unwrap().num_rows(), 1);
567 for invalid in [list.clone(), structure.clone()] {
568 match rows.rows[0].values[0].value_data.as_mut().unwrap() {
569 ValueData::ListValue(v) => v.items = vec![invalid],
570 ValueData::StructValue(v) => v.items = vec![invalid],
571 _ => unreachable!(),
572 }
573 assert!(matches!(
574 rows_to_record_batch(&rows, &table),
575 Err(error::Error::InvalidInsertRequest { .. })
576 ));
577 }
578 }
579 let datatype = ColumnDataTypeWrapper::list_datatype(ColumnDataTypeWrapper::list_datatype(
580 ColumnDataTypeWrapper::int32_datatype(),
581 ));
582 let (kind, extension) = datatype.to_parts();
583 let value = Value {
584 value_data: Some(ValueData::ListValue(api::v1::ListValue {
585 items: vec![list],
586 })),
587 };
588 assert!(value_matches_type(&value, kind as i32, extension.as_ref()));
589 assert!(!value_matches_type(&value, kind as i32, None));
590 }
591}