1pub mod scalar;
19
20use std::collections::HashMap;
21use std::sync::Arc;
22
23use itertools::Itertools;
24
25use crate::arrow::datatypes::{
26 DataType as ArrowDataType, Field as ArrowField, Schema as ArrowSchema,
27 SchemaRef as ArrowSchemaRef, TimeUnit,
28};
29use crate::arrow::error::ArrowError;
30use crate::error::KernelError;
31use crate::parquet::arrow::PARQUET_FIELD_ID_META_KEY;
32use crate::schema::{
33 ArrayType, ColumnMetadataKey, DataType, MapType, MetadataValue, PrimitiveType, StructField,
34 StructType,
35};
36
37pub(crate) const LIST_ARRAY_ROOT: &str = "element";
38pub(crate) const MAP_ROOT_DEFAULT: &str = "key_value";
39pub(crate) const MAP_KEY_DEFAULT: &str = "key";
40pub(crate) const MAP_VALUE_DEFAULT: &str = "value";
41
42pub(crate) fn kernel_flat_parquet_id_to_arrow_metadata(
46 field: &StructField,
47) -> Result<HashMap<String, String>, ArrowError> {
48 field
49 .metadata()
50 .iter()
51 .map(|(key, val)| {
52 let transformed_key = if key == ColumnMetadataKey::ParquetFieldId.as_ref() {
53 PARQUET_FIELD_ID_META_KEY.to_string()
54 } else {
55 key.clone()
56 };
57 match val {
58 MetadataValue::String(s) => Ok((transformed_key, s.clone())),
59 _ => Ok((
60 transformed_key,
61 serde_json::to_string(val).map_err(|e| ArrowError::JsonError(e.to_string()))?,
62 )),
63 }
64 })
65 .collect()
66}
67
68pub(crate) fn lookup_nested_field_id(
90 field: &StructField,
91 path: &str,
92) -> Result<Option<i64>, ArrowError> {
93 let key = ColumnMetadataKey::ColumnMappingNestedIds.as_ref();
94 let Some(value) = field.metadata().get(key) else {
95 return Ok(None);
96 };
97 let MetadataValue::Other(serde_json::Value::Object(obj)) = value else {
98 return Err(ArrowError::SchemaError(format!(
99 "'{key}' on field '{}' must be a JSON object",
100 field.name()
101 )));
102 };
103 obj.get(path).map_or(Ok(None), |entry| {
107 entry.as_i64().map(Some).ok_or_else(|| {
108 ArrowError::SchemaError(format!(
109 "'{key}['{path}']' on field '{}' must be an integer, got {entry}",
110 field.name()
111 ))
112 })
113 })
114}
115
116pub(crate) fn parquet_field_id_metadata(id: Option<i64>) -> HashMap<String, String> {
119 let mut meta = HashMap::new();
120 if let Some(id) = id {
121 meta.insert(PARQUET_FIELD_ID_META_KEY.to_string(), id.to_string());
122 }
123 meta
124}
125
126pub trait TryIntoArrow<ArrowType> {
129 fn try_into_arrow(self) -> Result<ArrowType, ArrowError>;
130}
131
132pub trait TryFromArrow<ArrowType>: Sized {
135 fn try_from_arrow(t: ArrowType) -> Result<Self, ArrowError>;
136}
137
138pub trait TryIntoKernel<KernelType> {
141 fn try_into_kernel(self) -> Result<KernelType, ArrowError>;
142}
143
144pub trait TryFromKernel<KernelType>: Sized {
147 fn try_from_kernel(t: KernelType) -> Result<Self, ArrowError>;
148}
149
150impl<KernelType, ArrowType> TryIntoArrow<ArrowType> for KernelType
151where
152 ArrowType: TryFromKernel<KernelType>,
153{
154 fn try_into_arrow(self) -> Result<ArrowType, ArrowError> {
155 ArrowType::try_from_kernel(self)
156 }
157}
158
159impl<KernelType, ArrowType> TryIntoKernel<KernelType> for ArrowType
160where
161 KernelType: TryFromArrow<ArrowType>,
162{
163 fn try_into_kernel(self) -> Result<KernelType, ArrowError> {
164 KernelType::try_from_arrow(self)
165 }
166}
167
168fn try_kernel_struct_to_arrow_fields(s: &StructType) -> Result<Vec<ArrowField>, ArrowError> {
170 s.fields().map(|f| f.try_into_arrow()).try_collect()
171}
172
173impl TryFromKernel<&StructType> for ArrowSchema {
174 fn try_from_kernel(s: &StructType) -> Result<Self, ArrowError> {
175 Ok(ArrowSchema::new(try_kernel_struct_to_arrow_fields(s)?))
176 }
177}
178
179impl TryFromKernel<&StructField> for ArrowField {
180 fn try_from_kernel(f: &StructField) -> Result<Self, ArrowError> {
181 let mut metadata = kernel_flat_parquet_id_to_arrow_metadata(f)?;
182 metadata.remove(ColumnMetadataKey::ColumnMappingNestedIds.as_ref());
185 let arrow_type = kernel_field_into_arrow(f, f.name(), f.data_type())?;
186 Ok(ArrowField::new(f.name(), arrow_type, f.is_nullable()).with_metadata(metadata))
187 }
188}
189
190fn kernel_field_into_arrow(
262 ancestor: &StructField,
263 relative_path: &str,
264 datatype: &DataType,
265) -> Result<ArrowDataType, ArrowError> {
266 match datatype {
267 #[cfg(feature = "udt-in-dev")]
268 DataType::UserDefined(udt) => udt.sql_type().try_into_arrow(),
269 DataType::Array(a) => {
270 let element_path = format!("{relative_path}.{LIST_ARRAY_ROOT}");
271 let element_id = lookup_nested_field_id(ancestor, &element_path)?;
272 let arrow_element_type =
273 kernel_field_into_arrow(ancestor, &element_path, a.element_type())?;
274 let arrow_element_field =
277 ArrowField::new(LIST_ARRAY_ROOT, arrow_element_type, a.contains_null())
278 .with_metadata(parquet_field_id_metadata(element_id));
279 Ok(ArrowDataType::List(Arc::new(arrow_element_field)))
280 }
281 DataType::Map(m) => {
282 let key_path = format!("{relative_path}.{MAP_KEY_DEFAULT}");
283 let value_path = format!("{relative_path}.{MAP_VALUE_DEFAULT}");
284 let key_id = lookup_nested_field_id(ancestor, &key_path)?;
285 let value_id = lookup_nested_field_id(ancestor, &value_path)?;
286 let arrow_key_type = kernel_field_into_arrow(ancestor, &key_path, m.key_type())?;
287 let arrow_value_type = kernel_field_into_arrow(ancestor, &value_path, m.value_type())?;
288 let arrow_key_field = ArrowField::new(MAP_KEY_DEFAULT, arrow_key_type, false)
290 .with_metadata(parquet_field_id_metadata(key_id));
291 let arrow_value_field =
292 ArrowField::new(MAP_VALUE_DEFAULT, arrow_value_type, m.value_contains_null())
293 .with_metadata(parquet_field_id_metadata(value_id));
294 let arrow_entries_field = ArrowField::new(
297 MAP_ROOT_DEFAULT,
298 ArrowDataType::Struct(vec![arrow_key_field, arrow_value_field].into()),
299 false, );
301 Ok(ArrowDataType::Map(
302 Arc::new(arrow_entries_field),
303 false, ))
305 }
306 DataType::Struct(_) | DataType::Primitive(_) | DataType::Variant(_) => {
307 datatype.try_into_arrow()
308 }
309 }
310}
311
312impl TryFromKernel<&ArrayType> for ArrowField {
313 fn try_from_kernel(a: &ArrayType) -> Result<Self, ArrowError> {
314 Ok(ArrowField::new(
315 LIST_ARRAY_ROOT,
316 a.element_type().try_into_arrow()?,
317 a.contains_null(),
318 ))
319 }
320}
321
322impl TryFromKernel<&MapType> for ArrowField {
323 fn try_from_kernel(a: &MapType) -> Result<Self, ArrowError> {
324 Ok(ArrowField::new(
325 MAP_ROOT_DEFAULT,
326 ArrowDataType::Struct(
327 vec![
328 ArrowField::new(MAP_KEY_DEFAULT, a.key_type().try_into_arrow()?, false),
329 ArrowField::new(
330 MAP_VALUE_DEFAULT,
331 a.value_type().try_into_arrow()?,
332 a.value_contains_null(),
333 ),
334 ]
335 .into(),
336 ),
337 false, ))
339 }
340}
341
342impl TryFromKernel<&DataType> for ArrowDataType {
343 fn try_from_kernel(t: &DataType) -> Result<Self, ArrowError> {
344 match t {
345 #[cfg(feature = "udt-in-dev")]
346 DataType::UserDefined(_) => t.physical_type().try_into_arrow(),
347 DataType::Primitive(p) => {
348 match p {
349 PrimitiveType::String => Ok(ArrowDataType::Utf8),
350 PrimitiveType::Long => Ok(ArrowDataType::Int64), PrimitiveType::Integer => Ok(ArrowDataType::Int32),
352 PrimitiveType::Short => Ok(ArrowDataType::Int16),
353 PrimitiveType::Byte => Ok(ArrowDataType::Int8),
354 PrimitiveType::Float => Ok(ArrowDataType::Float32),
355 PrimitiveType::Double => Ok(ArrowDataType::Float64),
356 PrimitiveType::Boolean => Ok(ArrowDataType::Boolean),
357 PrimitiveType::Binary => Ok(ArrowDataType::Binary),
358 PrimitiveType::Decimal(dtype) => Ok(ArrowDataType::Decimal128(
359 dtype.precision(),
360 dtype.scale() as i8, )),
362 PrimitiveType::Date => {
363 Ok(ArrowDataType::Date32)
366 }
367 PrimitiveType::Timestamp => Ok(ArrowDataType::Timestamp(
369 TimeUnit::Microsecond,
370 Some("UTC".into()),
371 )),
372 PrimitiveType::TimestampNtz => {
373 Ok(ArrowDataType::Timestamp(TimeUnit::Microsecond, None))
374 }
375 PrimitiveType::Void => Ok(ArrowDataType::Null),
376 PrimitiveType::IntervalYearMonth => Ok(ArrowDataType::Int32),
377 PrimitiveType::IntervalDayTime => Ok(ArrowDataType::Int64),
378 #[cfg(feature = "geo-type-in-dev")]
379 PrimitiveType::Geometry(_) | PrimitiveType::Geography(_) => {
380 Err(ArrowError::SchemaError(format!(
381 "Geo types are not yet supported in the default engine: {p}"
382 )))
383 }
384 }
385 }
386 DataType::Struct(s) => Ok(ArrowDataType::Struct(
387 try_kernel_struct_to_arrow_fields(s)?.into(),
388 )),
389 DataType::Array(a) => Ok(ArrowDataType::List(Arc::new(a.as_ref().try_into_arrow()?))),
390 DataType::Map(m) => Ok(ArrowDataType::Map(
391 Arc::new(m.as_ref().try_into_arrow()?),
392 false,
393 )),
394 DataType::Variant(s) => {
395 if *t == DataType::unshredded_variant() {
396 Ok(ArrowDataType::Struct(
397 try_kernel_struct_to_arrow_fields(s)?.into(),
398 ))
399 } else {
400 Err(ArrowError::SchemaError(format!(
401 "Incorrect Variant Schema: {t}. Only the unshredded variant schema is supported right now."
402 )))
403 }
404 }
405 }
406 }
407}
408
409impl TryFromArrow<&ArrowSchema> for StructType {
410 fn try_from_arrow(arrow_schema: &ArrowSchema) -> Result<Self, ArrowError> {
411 StructType::try_from_results(
412 arrow_schema
413 .fields()
414 .iter()
415 .map(|field| field.as_ref().try_into_kernel()),
416 )
417 .map_err(|e| ArrowError::from_external_error(e.into()))
418 }
419}
420
421impl TryFromArrow<ArrowSchemaRef> for StructType {
422 fn try_from_arrow(arrow_schema: ArrowSchemaRef) -> Result<Self, ArrowError> {
423 arrow_schema.as_ref().try_into_kernel()
424 }
425}
426
427impl TryFromArrow<&ArrowField> for StructField {
428 fn try_from_arrow(arrow_field: &ArrowField) -> Result<Self, ArrowError> {
429 let arrow_metadata = arrow_field.metadata();
430 if let (Some(arrow_id), Some(kernel_id)) = (
434 arrow_metadata.get(PARQUET_FIELD_ID_META_KEY),
435 arrow_metadata.get(ColumnMetadataKey::ParquetFieldId.as_ref()),
436 ) {
437 if arrow_id != kernel_id {
438 return Err(ArrowError::SchemaError(format!(
439 "Field '{}': conflicting parquet field IDs: '{}' ({}) vs '{}' ({})",
440 arrow_field.name(),
441 arrow_id,
442 PARQUET_FIELD_ID_META_KEY,
443 kernel_id,
444 ColumnMetadataKey::ParquetFieldId.as_ref(),
445 )));
446 }
447 }
448 let mut kernel_metadata: HashMap<String, MetadataValue> = arrow_metadata
449 .iter()
450 .map(|(k, v)| -> Result<_, ArrowError> {
451 if k == PARQUET_FIELD_ID_META_KEY || k == ColumnMetadataKey::ParquetFieldId.as_ref()
454 {
455 let id: i64 = v.parse().map_err(|_| {
456 ArrowError::SchemaError(format!(
457 "'{k}' on field '{}' must be an integer, got '{v}'",
458 arrow_field.name()
459 ))
460 })?;
461 return Ok((
462 ColumnMetadataKey::ParquetFieldId.as_ref().to_string(),
463 MetadataValue::Number(id),
464 ));
465 }
466 Ok((k.clone(), MetadataValue::from(v)))
467 })
468 .collect::<Result<_, _>>()?;
469 let mut nested_ids = serde_json::Map::new();
470 collect_nested_field_ids_from_arrow(
471 arrow_field.data_type(),
472 arrow_field.name(),
473 &mut nested_ids,
474 )?;
475 if !nested_ids.is_empty() {
476 kernel_metadata.insert(
477 ColumnMetadataKey::ColumnMappingNestedIds
478 .as_ref()
479 .to_string(),
480 MetadataValue::Other(serde_json::Value::Object(nested_ids)),
481 );
482 }
483
484 Ok(StructField::new(
485 arrow_field.name().clone(),
486 DataType::try_from_arrow(arrow_field.data_type())?,
487 arrow_field.is_nullable(),
488 )
489 .with_metadata(kernel_metadata))
490 }
491}
492
493fn collect_nested_field_ids_from_arrow(
509 arrow_type: &ArrowDataType,
510 relative_path: &str,
511 out: &mut serde_json::Map<String, serde_json::Value>,
512) -> Result<(), ArrowError> {
513 let inner_fields: Vec<(&ArrowField, &str)> = match arrow_type {
516 ArrowDataType::List(elem)
517 | ArrowDataType::ListView(elem)
518 | ArrowDataType::LargeList(elem)
519 | ArrowDataType::LargeListView(elem) => vec![(elem.as_ref(), LIST_ARRAY_ROOT)],
520 ArrowDataType::FixedSizeList(elem, _) => vec![(elem.as_ref(), LIST_ARRAY_ROOT)],
521 ArrowDataType::Map(entries, _) => match entries.data_type() {
522 ArrowDataType::Struct(fields) if fields.len() == 2 => vec![
523 (fields[0].as_ref(), MAP_KEY_DEFAULT),
524 (fields[1].as_ref(), MAP_VALUE_DEFAULT),
525 ],
526 other => {
527 return Err(ArrowError::SchemaError(format!(
528 "Map entries must be a struct with exactly 2 fields (key, value), got {other}"
529 )))
530 }
531 },
532 ArrowDataType::Dictionary(_, value_type) => {
535 return collect_nested_field_ids_from_arrow(value_type, relative_path, out);
536 }
537 _ => return Ok(()),
538 };
539
540 for (inner, segment) in inner_fields {
541 let path = format!("{relative_path}.{segment}");
542 if let Some(id_str) = inner.metadata().get(PARQUET_FIELD_ID_META_KEY) {
543 let id: i64 = id_str.parse().map_err(|_| {
544 ArrowError::SchemaError(format!(
545 "'{PARQUET_FIELD_ID_META_KEY}' on '{}' must be an integer, got '{id_str}'",
546 inner.name()
547 ))
548 })?;
549 out.insert(path.clone(), serde_json::Value::from(id));
550 }
551 collect_nested_field_ids_from_arrow(inner.data_type(), &path, out)?;
552 }
553 Ok(())
554}
555
556impl TryFromArrow<&ArrowDataType> for DataType {
557 fn try_from_arrow(arrow_datatype: &ArrowDataType) -> Result<Self, ArrowError> {
558 match arrow_datatype {
559 ArrowDataType::Utf8 => Ok(DataType::STRING),
560 ArrowDataType::LargeUtf8 => Ok(DataType::STRING),
561 ArrowDataType::Utf8View => Ok(DataType::STRING),
562 ArrowDataType::Int64 => Ok(DataType::LONG), ArrowDataType::Int32 => Ok(DataType::INTEGER),
564 ArrowDataType::Int16 => Ok(DataType::SHORT),
565 ArrowDataType::Int8 => Ok(DataType::BYTE),
566 ArrowDataType::UInt64 => Ok(DataType::LONG), ArrowDataType::UInt32 => Ok(DataType::INTEGER),
568 ArrowDataType::UInt16 => Ok(DataType::SHORT),
569 ArrowDataType::UInt8 => Ok(DataType::BYTE),
570 ArrowDataType::Null => Ok(DataType::VOID),
571 ArrowDataType::Float32 => Ok(DataType::FLOAT),
572 ArrowDataType::Float64 => Ok(DataType::DOUBLE),
573 ArrowDataType::Boolean => Ok(DataType::BOOLEAN),
574 ArrowDataType::Binary => Ok(DataType::BINARY),
575 ArrowDataType::FixedSizeBinary(_) => Ok(DataType::BINARY),
576 ArrowDataType::LargeBinary => Ok(DataType::BINARY),
577 ArrowDataType::BinaryView => Ok(DataType::BINARY),
578 ArrowDataType::Decimal128(p, s) => {
579 if *s < 0 {
580 return Err(ArrowError::from_external_error(
581 KernelError::invalid_decimal("Negative scales are not supported in Delta")
582 .into(),
583 ));
584 };
585 DataType::decimal(*p, *s as u8)
586 .map_err(|e| ArrowError::from_external_error(e.into()))
587 }
588 ArrowDataType::Date32 => Ok(DataType::DATE),
589 ArrowDataType::Date64 => Ok(DataType::DATE),
590 ArrowDataType::Timestamp(TimeUnit::Microsecond, None) => Ok(DataType::TIMESTAMP_NTZ),
591 ArrowDataType::Timestamp(TimeUnit::Microsecond, Some(tz))
592 if tz.eq_ignore_ascii_case("utc") =>
593 {
594 Ok(DataType::TIMESTAMP)
595 }
596 ArrowDataType::Timestamp(TimeUnit::Nanosecond, None) => Ok(DataType::TIMESTAMP_NTZ),
597 ArrowDataType::Timestamp(TimeUnit::Nanosecond, Some(tz))
598 if tz.eq_ignore_ascii_case("utc") =>
599 {
600 Ok(DataType::TIMESTAMP)
601 }
602 ArrowDataType::Timestamp(TimeUnit::Millisecond, None) => Ok(DataType::TIMESTAMP_NTZ),
606 ArrowDataType::Timestamp(TimeUnit::Millisecond, Some(tz))
607 if tz.eq_ignore_ascii_case("utc") =>
608 {
609 Ok(DataType::TIMESTAMP)
610 }
611 ArrowDataType::Struct(fields) => DataType::try_struct_type_from_results(
612 fields.iter().map(|field| field.as_ref().try_into_kernel()),
613 )
614 .map_err(|e| ArrowError::from_external_error(e.into())),
615 ArrowDataType::List(field) => Ok(ArrayType::new(
616 DataType::try_from_arrow((*field).data_type())?,
617 (*field).is_nullable(),
618 )
619 .into()),
620 ArrowDataType::ListView(field) => Ok(ArrayType::new(
621 DataType::try_from_arrow((*field).data_type())?,
622 (*field).is_nullable(),
623 )
624 .into()),
625 ArrowDataType::LargeList(field) => Ok(ArrayType::new(
626 DataType::try_from_arrow((*field).data_type())?,
627 (*field).is_nullable(),
628 )
629 .into()),
630 ArrowDataType::LargeListView(field) => Ok(ArrayType::new(
631 DataType::try_from_arrow((*field).data_type())?,
632 (*field).is_nullable(),
633 )
634 .into()),
635 ArrowDataType::FixedSizeList(field, _) => Ok(ArrayType::new(
636 DataType::try_from_arrow((*field).data_type())?,
637 (*field).is_nullable(),
638 )
639 .into()),
640 ArrowDataType::Map(field, _) => {
641 if let ArrowDataType::Struct(struct_fields) = field.data_type() {
642 let key_type = DataType::try_from_arrow(struct_fields[0].data_type())?;
643 let value_type = DataType::try_from_arrow(struct_fields[1].data_type())?;
644 let value_type_nullable = struct_fields[1].is_nullable();
645 Ok(MapType::new(key_type, value_type, value_type_nullable).into())
646 } else {
647 unreachable!("DataType::Map should contain a struct field child");
648 }
649 }
650 ArrowDataType::Dictionary(_, value_type) => {
653 Ok(value_type.as_ref().try_into_kernel()?)
654 }
655 s => Err(ArrowError::SchemaError(format!(
656 "Invalid data type for Delta Lake: {s}"
657 ))),
658 }
659 }
660}
661
662#[cfg(test)]
663mod tests {
664 use std::collections::HashMap;
665
666 use rstest::rstest;
667
668 use super::*;
669 use crate::engine::arrow_conversion::ArrowField;
670 use crate::engine::arrow_data::unshredded_variant_arrow_type;
671 use crate::parquet::arrow::PARQUET_FIELD_ID_META_KEY;
672 #[cfg(feature = "geo-type-in-dev")]
673 use crate::schema::EdgeInterpolationAlgorithm;
674 use crate::schema::{
675 schema, ArrayType, ColumnMetadataKey, DataType, MapType, MetadataValue, PrimitiveType,
676 StructField, StructType,
677 };
678 use crate::transforms::{transform_output_type, SchemaTransform};
679 use crate::unit_test_utils::{
680 array_in_map_kernel_schema, assert_result_error_with_message, collect_arrow_field_metadata,
681 complex_nested_with_field_ids,
682 };
683 #[cfg(feature = "geo-type-in-dev")]
684 use crate::unit_test_utils::{geography_type, geometry_type};
685 use crate::KernelResult;
686
687 #[cfg(feature = "geo-type-in-dev")]
688 #[rstest]
689 #[case(geometry_type("EPSG:4326"))]
690 #[case(geography_type("EPSG:4326", EdgeInterpolationAlgorithm::Spherical))]
691 #[case(DataType::from(schema! {
692 nullable "g": (geometry_type("EPSG:4326")),
693 }))]
694 #[case(DataType::from(ArrayType::new(geometry_type("EPSG:4326"), true)))]
695 #[case(DataType::from(MapType::new(
696 DataType::STRING,
697 geography_type("EPSG:4326", EdgeInterpolationAlgorithm::Spherical),
698 true,
699 )))]
700 fn test_geo_type_arrow_conversion_unsupported(#[case] dt: DataType) {
701 let result: KernelResult<ArrowDataType, _> = (&dt).try_into_arrow();
702 let err = result.unwrap_err();
703 assert!(matches!(err, ArrowError::SchemaError(_)), "got: {err:?}");
704 }
705
706 #[test]
707 fn test_metadata_string_conversion() -> KernelResult<()> {
708 let mut metadata = HashMap::new();
709 metadata.insert("description", "hello world".to_owned());
710 let struct_field = StructField::not_null("name", DataType::STRING).with_metadata(metadata);
711
712 let arrow_field = ArrowField::try_from_kernel(&struct_field)?;
713 let new_metadata = arrow_field.metadata();
714
715 assert_eq!(
716 new_metadata.get("description").unwrap(),
717 &"hello world".to_owned()
718 );
719 Ok(())
720 }
721
722 #[test]
725 fn test_void_type_roundtrip() -> KernelResult<()> {
726 let json = r#"
727 {
728 "name": "void_col",
729 "type": "void",
730 "nullable": true,
731 "metadata": {}
732 }
733 "#;
734
735 let field: crate::schema::StructField = serde_json::from_str(json).unwrap();
736 assert_eq!(field.data_type, DataType::Primitive(PrimitiveType::Void));
737
738 let arrow_type = ArrowDataType::try_from_kernel(&field.data_type)?;
739 assert_eq!(arrow_type, ArrowDataType::Null);
740
741 let kernel_type = DataType::try_from_arrow(&ArrowDataType::Null)?;
742 assert_eq!(kernel_type, DataType::Primitive(PrimitiveType::Void));
743
744 Ok(())
745 }
746
747 #[rstest]
749 #[case(DataType::INTERVAL_YEAR_MONTH, ArrowDataType::Int32)]
750 #[case(DataType::INTERVAL_DAY_TIME, ArrowDataType::Int64)]
751 fn test_interval_type_arrow_conversion(
752 #[case] kernel_type: DataType,
753 #[case] expected: ArrowDataType,
754 ) -> KernelResult<()> {
755 assert_eq!(ArrowDataType::try_from_kernel(&kernel_type)?, expected);
756 Ok(())
757 }
758
759 #[test]
762 fn test_millisecond_timestamp_conversion() -> KernelResult<()> {
763 let utc = DataType::try_from_arrow(&ArrowDataType::Timestamp(
764 TimeUnit::Millisecond,
765 Some("UTC".into()),
766 ))?;
767 assert_eq!(utc, DataType::TIMESTAMP);
768
769 let ntz = DataType::try_from_arrow(&ArrowDataType::Timestamp(TimeUnit::Millisecond, None))?;
770 assert_eq!(ntz, DataType::TIMESTAMP_NTZ);
771
772 Ok(())
773 }
774
775 #[test]
779 fn test_void_type_not_nullable() -> KernelResult<()> {
780 let json = r#"
781 {
782 "name": "void_col",
783 "type": "void",
784 "nullable": false,
785 "metadata": {}
786 }
787 "#;
788
789 let field: crate::schema::StructField = serde_json::from_str(json).unwrap();
790 assert_eq!(field.data_type, DataType::Primitive(PrimitiveType::Void));
791 assert!(!field.is_nullable());
792
793 let arrow_field = ArrowField::try_from_kernel(&field)?;
795 assert_eq!(arrow_field.data_type(), &ArrowDataType::Null);
796
797 assert!(!arrow_field.is_nullable());
800
801 Ok(())
802 }
803
804 #[test]
805 fn test_void_field_in_struct() -> KernelResult<()> {
806 let schema = schema! {
808 nullable "id": INTEGER,
809 nullable "void_col": VOID,
810 };
811
812 let arrow_schema = ArrowSchema::try_from_kernel(&schema)?;
813 assert_eq!(arrow_schema.fields().len(), 2);
814 assert_eq!(arrow_schema.field(0).data_type(), &ArrowDataType::Int32);
815 assert_eq!(arrow_schema.field(1).data_type(), &ArrowDataType::Null);
816 assert_eq!(arrow_schema.field(1).name(), "void_col");
817
818 let kernel_schema = StructType::try_from_arrow(&arrow_schema)?;
820 assert_eq!(
821 kernel_schema.field("void_col").unwrap().data_type,
822 DataType::VOID
823 );
824
825 Ok(())
826 }
827
828 #[test]
829 fn test_variant_shredded_type_fail() -> KernelResult<()> {
830 let unshredded_variant = DataType::unshredded_variant();
831 let unshredded_variant_arrow = ArrowDataType::try_from_kernel(&unshredded_variant)?;
832 assert!(unshredded_variant_arrow == unshredded_variant_arrow_type());
833 let shredded_variant = DataType::variant_type([
834 StructField::nullable("metadata", DataType::BINARY),
835 StructField::nullable("value", DataType::BINARY),
836 StructField::nullable("typed_value", DataType::INTEGER),
837 ])?;
838 let shredded_variant_arrow = ArrowDataType::try_from_kernel(&shredded_variant);
839 assert!(shredded_variant_arrow
840 .unwrap_err()
841 .to_string()
842 .contains("Incorrect Variant Schema"));
843 Ok(())
844 }
845
846 #[derive(Default)]
848 struct FieldIdCollector {
849 field_ids: Vec<(String, String)>, }
851
852 impl<'a> SchemaTransform<'a> for FieldIdCollector {
853 transform_output_type!(|'a, T| ());
854
855 fn transform_struct_field(&mut self, field: &'a StructField) {
856 if let Some(field_id) = field
858 .metadata()
859 .get(ColumnMetadataKey::ParquetFieldId.as_ref())
860 {
861 self.field_ids
862 .push((field.name().to_string(), field_id.to_string()));
863 }
864 self.recurse_into_struct_field(field)
866 }
867 }
868
869 #[test]
870 fn test_try_into_arrow_threads_nested_ids_onto_arrow_schema() -> KernelResult<()> {
871 let meta_key = ColumnMetadataKey::ColumnMappingNestedIds.as_ref();
872 let fixture = complex_nested_with_field_ids(meta_key);
873 let arrow_schema: ArrowSchema = (&fixture.kernel_schema).try_into_arrow()?;
874
875 assert_eq!(
876 arrow_schema, fixture.expected_arrow_schema,
877 "try_into_arrow should attach nested field ids for metadata key {meta_key}",
878 );
879 Ok(())
880 }
881
882 #[rstest]
883 #[case::without_nested_id_metadata(array_in_map_kernel_schema(std::iter::empty::<(
885 String,
886 MetadataValue,
887 )>()), &[])]
888 #[case::only_partial_nested_ids_match(array_in_map_kernel_schema([(
890 ColumnMetadataKey::ColumnMappingNestedIds.as_ref().to_string(),
891 MetadataValue::Other(test_utils::nested_ids_json(&[
892 ("array_in_map.key", 100),
893 ("array_in_map.value.element", 102),
894 ("array_in_map.notTheKey", 999),
895 ])),
896 )]), &[("element", "102"), ("key", "100")])]
897 fn test_try_into_arrow_sets_field_ids_for_matched_nested_ids_only(
898 #[case] schema: StructType,
899 #[case] expected_field_ids: &[(&str, &str)],
900 ) -> KernelResult<()> {
901 let arrow_schema: ArrowSchema = (&schema).try_into_arrow()?;
902 let field_ids: HashMap<String, String> =
903 collect_arrow_field_metadata(&arrow_schema, PARQUET_FIELD_ID_META_KEY)
904 .into_iter()
905 .collect();
906
907 assert_eq!(field_ids.len(), expected_field_ids.len());
908 for (field_name, expected_id) in expected_field_ids {
909 assert_eq!(
910 field_ids.get(*field_name).map(String::as_str),
911 Some(*expected_id),
912 );
913 }
914 Ok(())
915 }
916
917 #[test]
918 fn test_try_into_arrow_invalid_nested_ids_metadata_errors() {
919 let schema = array_in_map_kernel_schema([(
920 ColumnMetadataKey::ColumnMappingNestedIds
921 .as_ref()
922 .to_string(),
923 MetadataValue::String("not a json object".to_string()),
924 )]);
925 assert_result_error_with_message(
926 ArrowSchema::try_from_kernel(&schema),
927 "must be a JSON object",
928 );
929
930 let schema = array_in_map_kernel_schema([(
931 ColumnMetadataKey::ColumnMappingNestedIds
932 .as_ref()
933 .to_string(),
934 MetadataValue::Other(serde_json::json!({ "array_in_map.key": "oops" })),
935 )]);
936 assert_result_error_with_message(
937 ArrowSchema::try_from_kernel(&schema),
938 "must be an integer",
939 );
940 }
941
942 #[test]
943 fn test_recursive_field_id_transformation() -> KernelResult<()> {
944 let inner_struct_type = schema! {
962 (StructField::not_null("inner_field", DataType::STRING).with_metadata([(
963 ColumnMetadataKey::ParquetFieldId.as_ref(),
964 MetadataValue::Number(3),
965 )])),
966 };
967
968 let array_item_struct = schema! {
970 (StructField::not_null("array_item", DataType::INTEGER).with_metadata([(
971 ColumnMetadataKey::ParquetFieldId.as_ref(),
972 MetadataValue::Number(5),
973 )])),
974 };
975 let array_type = ArrayType::new(array_item_struct, false);
976
977 let map_key_struct = schema! {
979 (StructField::not_null("map_key_field", DataType::STRING).with_metadata([(
980 ColumnMetadataKey::ParquetFieldId.as_ref(),
981 MetadataValue::Number(7),
982 )])),
983 };
984 let map_value_struct = schema! {
985 (StructField::not_null("map_value_field", DataType::INTEGER).with_metadata([(
986 ColumnMetadataKey::ParquetFieldId.as_ref(),
987 MetadataValue::Number(8),
988 )])),
989 };
990 let map_type = MapType::new(map_key_struct, map_value_struct, false);
991
992 let top_struct = schema! {
994 (StructField::not_null("simple_field", DataType::INTEGER).with_metadata([(
995 ColumnMetadataKey::ParquetFieldId.as_ref(),
996 MetadataValue::Number(1),
997 )])),
998 (StructField::not_null("nested_struct", inner_struct_type).with_metadata([(
999 ColumnMetadataKey::ParquetFieldId.as_ref(),
1000 MetadataValue::Number(2),
1001 )])),
1002 (StructField::not_null("array_field", array_type).with_metadata([(
1003 ColumnMetadataKey::ParquetFieldId.as_ref(),
1004 MetadataValue::Number(4),
1005 )])),
1006 (StructField::not_null("map_field", map_type).with_metadata([(
1007 ColumnMetadataKey::ParquetFieldId.as_ref(),
1008 MetadataValue::Number(6),
1009 )])),
1010 };
1011
1012 let arrow_schema = ArrowSchema::try_from_kernel(&top_struct)?;
1014
1015 let expected_ids: HashMap<String, String> = [
1016 ("simple_field", "1"),
1017 ("nested_struct", "2"),
1018 ("inner_field", "3"),
1019 ("array_field", "4"),
1020 ("array_item", "5"),
1021 ("map_field", "6"),
1022 ("map_key_field", "7"),
1023 ("map_value_field", "8"),
1024 ]
1025 .into_iter()
1026 .map(|(k, v)| (k.to_string(), v.to_string()))
1027 .collect();
1028
1029 let arrow_field_ids: HashMap<String, String> =
1031 collect_arrow_field_metadata(&arrow_schema, PARQUET_FIELD_ID_META_KEY)
1032 .into_iter()
1033 .collect();
1034 assert_eq!(
1035 arrow_field_ids, expected_ids,
1036 "All field IDs should be transformed to PARQUET:field_id"
1037 );
1038
1039 let kernel_struct = StructType::try_from_arrow(&arrow_schema)?;
1041 let mut collector = FieldIdCollector::default();
1042 collector.transform_struct(&kernel_struct);
1043 let kernel_field_ids: HashMap<String, String> = collector.field_ids.into_iter().collect();
1044 assert_eq!(
1045 kernel_field_ids, arrow_field_ids,
1046 "Kernel field IDs should match Arrow field IDs after round-trip"
1047 );
1048
1049 Ok(())
1050 }
1051
1052 #[test]
1055 fn test_arrow_to_kernel_matching_field_ids_succeed() {
1056 let arrow_field =
1057 ArrowField::new("a", ArrowDataType::Int32, false).with_metadata(HashMap::from([
1058 (PARQUET_FIELD_ID_META_KEY.to_string(), "42".to_string()),
1059 (
1060 ColumnMetadataKey::ParquetFieldId.as_ref().to_string(),
1061 "42".to_string(),
1062 ),
1063 ]));
1064 let kernel = StructField::try_from_arrow(&arrow_field).unwrap();
1065 assert_eq!(
1068 kernel
1069 .metadata()
1070 .get(ColumnMetadataKey::ParquetFieldId.as_ref()),
1071 Some(&MetadataValue::Number(42)),
1072 );
1073 }
1074
1075 #[test]
1079 fn test_arrow_to_kernel_conflicting_field_ids_fail() {
1080 let arrow_field =
1081 ArrowField::new("a", ArrowDataType::Int32, false).with_metadata(HashMap::from([
1082 (PARQUET_FIELD_ID_META_KEY.to_string(), "1".to_string()),
1083 (
1084 ColumnMetadataKey::ParquetFieldId.as_ref().to_string(),
1085 "2".to_string(),
1086 ),
1087 ]));
1088 assert_result_error_with_message(
1089 StructField::try_from_arrow(&arrow_field),
1090 "conflicting parquet field IDs",
1091 );
1092 }
1093
1094 #[test]
1098 fn test_try_from_arrow_aggregates_synthesized_nested_ids() {
1099 let fixture =
1100 complex_nested_with_field_ids(ColumnMetadataKey::ColumnMappingNestedIds.as_ref());
1101 let kernel_schema_from_arrow: StructType =
1102 (&fixture.expected_arrow_schema).try_into_kernel().unwrap();
1103 assert_eq!(kernel_schema_from_arrow, fixture.kernel_schema);
1104 }
1105
1106 #[test]
1109 fn test_try_from_arrow_no_nested_ids_when_input_lacks_field_ids() {
1110 let kernel_schema_without_metadata =
1111 array_in_map_kernel_schema(std::iter::empty::<(String, MetadataValue)>());
1112 let arrow_schema_without_parquet_id: ArrowSchema =
1113 (&kernel_schema_without_metadata).try_into_arrow().unwrap();
1114
1115 let kernel_schema: StructType = (&arrow_schema_without_parquet_id)
1116 .try_into_kernel()
1117 .unwrap();
1118 let array_in_map = kernel_schema.fields().next().unwrap();
1119 assert!(!array_in_map
1120 .metadata()
1121 .contains_key(ColumnMetadataKey::ColumnMappingNestedIds.as_ref()));
1122 }
1123
1124 #[test]
1127 fn test_try_from_arrow_invalid_inner_field_id_errors() {
1128 let element_field =
1129 ArrowField::new("element", ArrowDataType::Int32, true).with_metadata(HashMap::from([
1130 (PARQUET_FIELD_ID_META_KEY.to_string(), "oops".to_string()),
1131 ]));
1132 let list_field = ArrowField::new("arr", ArrowDataType::List(Arc::new(element_field)), true);
1133 assert_result_error_with_message(
1134 StructField::try_from_arrow(&list_field),
1135 "must be an integer",
1136 );
1137 }
1138
1139 #[rstest]
1142 #[case::list(ArrowDataType::List(arc_elem_with_id(42)))]
1143 #[case::list_view(ArrowDataType::ListView(arc_elem_with_id(42)))]
1144 #[case::large_list(ArrowDataType::LargeList(arc_elem_with_id(42)))]
1145 #[case::large_list_view(ArrowDataType::LargeListView(arc_elem_with_id(42)))]
1146 #[case::fixed_size_list(ArrowDataType::FixedSizeList(arc_elem_with_id(42), 3))]
1147 fn test_try_from_arrow_aggregates_nested_id_for_all_list_kinds(
1148 #[case] dt: ArrowDataType,
1149 ) -> KernelResult<()> {
1150 let arrow_field = ArrowField::new("arr", dt, true);
1151 let kernel = StructField::try_from_arrow(&arrow_field)?;
1152 assert_eq!(
1153 kernel
1154 .metadata()
1155 .get(ColumnMetadataKey::ColumnMappingNestedIds.as_ref()),
1156 Some(&MetadataValue::Other(
1157 serde_json::json!({"arr.element": 42})
1158 )),
1159 );
1160 Ok(())
1161 }
1162
1163 #[test]
1166 fn test_try_from_arrow_aggregates_nested_id_through_dictionary() -> KernelResult<()> {
1167 let arrow_dict = ArrowDataType::Dictionary(
1168 Box::new(ArrowDataType::Int32),
1169 Box::new(ArrowDataType::List(arc_elem_with_id(7))),
1170 );
1171 let arrow_field = ArrowField::new("arr", arrow_dict, true);
1172 let kernel = StructField::try_from_arrow(&arrow_field)?;
1173 assert_eq!(
1174 kernel
1175 .metadata()
1176 .get(ColumnMetadataKey::ColumnMappingNestedIds.as_ref()),
1177 Some(&MetadataValue::Other(serde_json::json!({"arr.element": 7}))),
1178 );
1179 Ok(())
1180 }
1181
1182 fn arc_elem_with_id(id: i32) -> Arc<ArrowField> {
1183 Arc::new(
1184 ArrowField::new("element", ArrowDataType::Int32, true).with_metadata(HashMap::from([
1185 (PARQUET_FIELD_ID_META_KEY.to_string(), id.to_string()),
1186 ])),
1187 )
1188 }
1189}