1use std::sync::Arc;
17
18use arrow_schema::DataType;
19use arrow_schema::Field;
20use arrow_schema::FieldRef;
21use arrow_schema::Fields;
22use arrow_schema::Schema;
23use arrow_schema::SchemaBuilder;
24use arrow_schema::SchemaRef;
25use arrow_schema::TimeUnit as ArrowTimeUnit;
26use vortex_array::dtype::DType;
27use vortex_array::dtype::DecimalDType;
28use vortex_array::dtype::FieldName;
29use vortex_array::dtype::Nullability;
30use vortex_array::dtype::PType;
31use vortex_array::dtype::StructFields;
32use vortex_array::extension::datetime::AnyTemporal;
33use vortex_array::extension::datetime::Date;
34use vortex_array::extension::datetime::TemporalMetadata;
35use vortex_array::extension::datetime::Time;
36use vortex_array::extension::datetime::TimeUnit;
37use vortex_array::extension::datetime::Timestamp;
38use vortex_error::VortexExpect;
39use vortex_error::VortexResult;
40use vortex_error::vortex_bail;
41use vortex_error::vortex_ensure;
42use vortex_error::vortex_ensure_eq;
43use vortex_error::vortex_err;
44use vortex_error::vortex_panic;
45
46pub trait FromArrowType<T>: Sized {
48 fn from_arrow(value: T) -> Self;
50}
51
52pub trait TryFromArrowType<T>: Sized {
54 fn try_from_arrow(value: T) -> VortexResult<Self>;
56}
57
58impl TryFromArrowType<&DataType> for PType {
59 fn try_from_arrow(value: &DataType) -> VortexResult<Self> {
60 match value {
61 DataType::Int8 => Ok(Self::I8),
62 DataType::Int16 => Ok(Self::I16),
63 DataType::Int32 => Ok(Self::I32),
64 DataType::Int64 => Ok(Self::I64),
65 DataType::UInt8 => Ok(Self::U8),
66 DataType::UInt16 => Ok(Self::U16),
67 DataType::UInt32 => Ok(Self::U32),
68 DataType::UInt64 => Ok(Self::U64),
69 DataType::Float16 => Ok(Self::F16),
70 DataType::Float32 => Ok(Self::F32),
71 DataType::Float64 => Ok(Self::F64),
72 _ => Err(vortex_err!(
73 "Arrow datatype {:?} cannot be converted to ptype",
74 value
75 )),
76 }
77 }
78}
79
80impl TryFromArrowType<&DataType> for DecimalDType {
81 fn try_from_arrow(value: &DataType) -> VortexResult<Self> {
82 match value {
83 DataType::Decimal32(precision, scale)
84 | DataType::Decimal64(precision, scale)
85 | DataType::Decimal128(precision, scale)
86 | DataType::Decimal256(precision, scale) => Self::try_new(*precision, *scale),
87
88 _ => Err(vortex_err!(
89 "Arrow datatype {:?} cannot be converted to DecimalDType",
90 value
91 )),
92 }
93 }
94}
95
96impl FromArrowType<&ArrowTimeUnit> for TimeUnit {
97 fn from_arrow(value: &ArrowTimeUnit) -> Self {
98 Self::from_arrow(*value)
99 }
100}
101
102impl FromArrowType<ArrowTimeUnit> for TimeUnit {
103 fn from_arrow(value: ArrowTimeUnit) -> Self {
104 match value {
105 ArrowTimeUnit::Second => Self::Seconds,
106 ArrowTimeUnit::Millisecond => Self::Milliseconds,
107 ArrowTimeUnit::Microsecond => Self::Microseconds,
108 ArrowTimeUnit::Nanosecond => Self::Nanoseconds,
109 }
110 }
111}
112
113pub fn to_arrow_time_unit(value: TimeUnit) -> VortexResult<ArrowTimeUnit> {
119 Ok(match value {
120 TimeUnit::Seconds => ArrowTimeUnit::Second,
121 TimeUnit::Milliseconds => ArrowTimeUnit::Millisecond,
122 TimeUnit::Microseconds => ArrowTimeUnit::Microsecond,
123 TimeUnit::Nanoseconds => ArrowTimeUnit::Nanosecond,
124 _ => vortex_bail!("Cannot convert {value} to Arrow TimeUnit"),
125 })
126}
127
128impl FromArrowType<SchemaRef> for DType {
129 fn from_arrow(value: SchemaRef) -> Self {
130 Self::from_arrow(value.as_ref())
131 }
132}
133
134impl TryFromArrowType<SchemaRef> for DType {
135 fn try_from_arrow(value: SchemaRef) -> VortexResult<Self> {
136 Self::try_from_arrow(value.as_ref())
137 }
138}
139
140impl FromArrowType<&Schema> for DType {
141 fn from_arrow(value: &Schema) -> Self {
142 Self::try_from_arrow(value).vortex_expect("arrow schema to dtype")
143 }
144}
145
146impl TryFromArrowType<&Schema> for DType {
147 fn try_from_arrow(value: &Schema) -> VortexResult<Self> {
148 Ok(Self::Struct(
149 StructFields::try_from_arrow(value.fields())?,
150 Nullability::NonNullable, ))
152 }
153}
154
155impl FromArrowType<&Fields> for StructFields {
156 fn from_arrow(value: &Fields) -> Self {
157 Self::try_from_arrow(value).vortex_expect("arrow fields to struct fields")
158 }
159}
160
161impl TryFromArrowType<&Fields> for StructFields {
162 fn try_from_arrow(value: &Fields) -> VortexResult<Self> {
163 value
164 .into_iter()
165 .map(|f| {
166 Ok((
167 FieldName::from(f.name().as_str()),
168 DType::try_from_arrow(f.as_ref())?,
169 ))
170 })
171 .collect::<VortexResult<StructFields>>()
172 }
173}
174
175impl FromArrowType<(&DataType, Nullability)> for DType {
176 fn from_arrow(value: (&DataType, Nullability)) -> Self {
177 Self::try_from_arrow(value).vortex_expect("arrow data type to dtype")
178 }
179}
180
181impl TryFromArrowType<(&DataType, Nullability)> for DType {
182 fn try_from_arrow((data_type, nullability): (&DataType, Nullability)) -> VortexResult<Self> {
183 if data_type.is_integer() || data_type.is_floating() {
184 return Ok(DType::Primitive(
185 PType::try_from_arrow(data_type)?,
186 nullability,
187 ));
188 }
189
190 Ok(match data_type {
191 DataType::Null => DType::Null,
192 DataType::Decimal32(precision, scale)
193 | DataType::Decimal64(precision, scale)
194 | DataType::Decimal128(precision, scale)
195 | DataType::Decimal256(precision, scale) => {
196 DType::Decimal(DecimalDType::new(*precision, *scale), nullability)
197 }
198 DataType::Boolean => DType::Bool(nullability),
199 DataType::Utf8 | DataType::LargeUtf8 | DataType::Utf8View => DType::Utf8(nullability),
200 DataType::Binary | DataType::LargeBinary | DataType::BinaryView => {
201 DType::Binary(nullability)
202 }
203 DataType::Date32 => DType::Extension(Date::new(TimeUnit::Days, nullability).erased()),
204 DataType::Date64 => {
205 DType::Extension(Date::new(TimeUnit::Milliseconds, nullability).erased())
206 }
207 DataType::Time32(unit) => {
208 DType::Extension(Time::new(TimeUnit::from_arrow(unit), nullability).erased())
209 }
210 DataType::Time64(unit) => {
211 DType::Extension(Time::new(TimeUnit::from_arrow(unit), nullability).erased())
212 }
213 DataType::Timestamp(unit, tz) => DType::Extension(
214 Timestamp::new_with_tz(TimeUnit::from_arrow(unit), tz.clone(), nullability)
215 .erased(),
216 ),
217 DataType::List(e)
218 | DataType::LargeList(e)
219 | DataType::ListView(e)
220 | DataType::LargeListView(e) => {
221 DType::List(Arc::new(Self::try_from_arrow(e.as_ref())?), nullability)
222 }
223 DataType::FixedSizeList(e, size) => DType::FixedSizeList(
224 Arc::new(Self::try_from_arrow(e.as_ref())?),
225 *size as u32,
226 nullability,
227 ),
228 DataType::Struct(f) => DType::Struct(StructFields::try_from_arrow(f)?, nullability),
229 DataType::Map(entries, keys_sorted) => {
230 vortex_ensure!(
231 !entries.is_nullable(),
232 "Arrow map entries field must be non-nullable"
233 );
234 let DataType::Struct(fields) = entries.data_type() else {
235 vortex_bail!(
236 "Arrow map entries field must have Struct type, got {:?}",
237 entries.data_type()
238 );
239 };
240 vortex_ensure_eq!(
241 fields.len(),
242 2,
243 InvalidArgument: "Arrow map entries struct must contain exactly two fields"
244 );
245 let key = &fields[0];
246 let value = &fields[1];
247 vortex_ensure!(
248 !key.is_nullable(),
249 "Arrow map key field must be non-nullable"
250 );
251 DType::map(
252 Self::try_from_arrow(key.as_ref())?,
253 Self::try_from_arrow(value.as_ref())?,
254 *keys_sorted,
255 nullability,
256 )?
257 }
258 DataType::Dictionary(_, value_type) => {
259 Self::try_from_arrow((value_type.as_ref(), nullability))?
260 }
261 DataType::RunEndEncoded(_, value_type) => {
262 Self::try_from_arrow((value_type.data_type(), nullability))?
263 }
264 _ => vortex_bail!("Arrow data type not supported: {data_type:?}"),
265 })
266 }
267}
268
269impl FromArrowType<&Field> for DType {
270 fn from_arrow(field: &Field) -> Self {
271 Self::try_from_arrow(field).vortex_expect("arrow field to dtype")
272 }
273}
274
275impl TryFromArrowType<&Field> for DType {
276 fn try_from_arrow(field: &Field) -> VortexResult<Self> {
277 if field
278 .metadata()
279 .get("ARROW:extension:name")
280 .map(|s| s.as_str())
281 == Some("arrow.parquet.variant")
282 {
283 return Ok(DType::Variant(field.is_nullable().into()));
284 }
285 Self::try_from_arrow((field.data_type(), field.is_nullable().into()))
286 }
287}
288
289pub trait ToArrowType {
294 fn to_arrow_schema(&self) -> VortexResult<Schema>;
301
302 #[deprecated(note = "Use `ArrowSession::to_arrow_datatype` instead")]
310 fn to_arrow_dtype(&self) -> VortexResult<DataType>;
311}
312
313impl ToArrowType for DType {
314 fn to_arrow_schema(&self) -> VortexResult<Schema> {
315 let DType::Struct(struct_dtype, nullable) = self else {
316 vortex_bail!("only DType::Struct can be converted to arrow schema");
317 };
318
319 if *nullable != Nullability::NonNullable {
320 vortex_bail!("top-level struct in Schema must be NonNullable");
321 }
322
323 let mut builder = SchemaBuilder::with_capacity(struct_dtype.names().len());
324 for (field_name, field_dtype) in struct_dtype.names().iter().zip(struct_dtype.fields()) {
325 let field = if field_dtype.is_variant() {
326 let storage = DataType::Struct(variant_storage_fields_minimal());
327 Field::new(field_name.as_ref(), storage, field_dtype.is_nullable()).with_metadata(
328 [(
329 "ARROW:extension:name".to_owned(),
330 "arrow.parquet.variant".to_owned(),
331 )]
332 .into(),
333 )
334 } else {
335 Field::new(
336 field_name.as_ref(),
337 to_data_type_naive(&field_dtype)?,
338 field_dtype.is_nullable(),
339 )
340 };
341 builder.push(field);
342 }
343
344 Ok(builder.finish())
345 }
346
347 fn to_arrow_dtype(&self) -> VortexResult<DataType> {
348 to_data_type_naive(self)
349 }
350}
351
352pub(crate) fn to_data_type_naive(dtype: &DType) -> VortexResult<DataType> {
354 Ok(match dtype {
355 DType::Null => DataType::Null,
356 DType::Bool(_) => DataType::Boolean,
357 DType::Primitive(ptype, _) => match ptype {
358 PType::U8 => DataType::UInt8,
359 PType::U16 => DataType::UInt16,
360 PType::U32 => DataType::UInt32,
361 PType::U64 => DataType::UInt64,
362 PType::I8 => DataType::Int8,
363 PType::I16 => DataType::Int16,
364 PType::I32 => DataType::Int32,
365 PType::I64 => DataType::Int64,
366 PType::F16 => DataType::Float16,
367 PType::F32 => DataType::Float32,
368 PType::F64 => DataType::Float64,
369 },
370 DType::Decimal(dt, _) => {
371 let precision = dt.precision();
372 let scale = dt.scale();
373
374 match precision {
375 0..=38 => DataType::Decimal128(precision, scale),
382 39.. => DataType::Decimal256(precision, scale),
384 }
385 }
386 DType::Utf8(_) => DataType::Utf8View,
387 DType::Binary(_) => DataType::BinaryView,
388 DType::List(elem_dtype, _) => DataType::List(FieldRef::new(Field::new_list_field(
392 to_data_type_naive(elem_dtype)?,
393 elem_dtype.nullability().into(),
394 ))),
395 DType::FixedSizeList(elem_dtype, size, _) => DataType::FixedSizeList(
396 FieldRef::new(Field::new_list_field(
397 to_data_type_naive(elem_dtype)?,
398 elem_dtype.nullability().into(),
399 )),
400 *size as i32,
401 ),
402 DType::Map(map_dtype, _) => {
403 let key = Field::new("key", to_data_type_naive(&map_dtype.key_dtype())?, false);
404 let value_dtype = map_dtype.value_dtype();
405 let value = Field::new(
406 "value",
407 to_data_type_naive(&value_dtype)?,
408 value_dtype.is_nullable(),
409 );
410 let entries = Field::new_struct("entries", Fields::from(vec![key, value]), false);
411 DataType::Map(FieldRef::new(entries), map_dtype.keys_sorted())
412 }
413 DType::Struct(struct_dtype, _) => {
414 let mut fields = Vec::with_capacity(struct_dtype.names().len());
415 for (field_name, field_dt) in struct_dtype.names().iter().zip(struct_dtype.fields()) {
416 fields.push(FieldRef::from(Field::new(
417 field_name.as_ref(),
418 to_data_type_naive(&field_dt)?,
419 field_dt.is_nullable(),
420 )));
421 }
422
423 DataType::Struct(Fields::from(fields))
424 }
425 DType::Union(..) => todo!("TODO(connor)[Union]: unimplemented"),
426 DType::Variant(_) => vortex_bail!(
427 "DType::Variant requires Arrow Field metadata; use to_arrow_schema or a Field helper"
428 ),
429 DType::Extension(ext_dtype) => {
430 if let Some(temporal) = ext_dtype.metadata_opt::<AnyTemporal>() {
435 return Ok(match temporal {
436 TemporalMetadata::Timestamp(unit, tz) => {
437 DataType::Timestamp(to_arrow_time_unit(*unit)?, tz.clone())
438 }
439 TemporalMetadata::Date(unit) => match unit {
440 TimeUnit::Days => DataType::Date32,
441 TimeUnit::Milliseconds => DataType::Date64,
442 TimeUnit::Nanoseconds | TimeUnit::Microseconds | TimeUnit::Seconds => {
443 vortex_panic!(InvalidArgument: "Invalid TimeUnit {} for {}", unit, ext_dtype.id())
444 }
445 },
446 TemporalMetadata::Time(unit) => match unit {
447 TimeUnit::Seconds => DataType::Time32(ArrowTimeUnit::Second),
448 TimeUnit::Milliseconds => DataType::Time32(ArrowTimeUnit::Millisecond),
449 TimeUnit::Microseconds => DataType::Time64(ArrowTimeUnit::Microsecond),
450 TimeUnit::Nanoseconds => DataType::Time64(ArrowTimeUnit::Nanosecond),
451 TimeUnit::Days => {
452 vortex_panic!(InvalidArgument: "Invalid TimeUnit {} for {}", unit, ext_dtype.id())
453 }
454 },
455 });
456 };
457
458 vortex_bail!("Unsupported extension type \"{}\"", ext_dtype.id())
459 }
460 })
461}
462
463fn variant_storage_fields_minimal() -> Fields {
464 Fields::from(vec![
465 Field::new("metadata", DataType::Binary, false),
466 Field::new("value", DataType::Binary, true),
467 ])
468}
469
470#[cfg(test)]
471mod test {
472 #![expect(deprecated, reason = "tests for deprecated methods")]
473 use arrow_schema::DataType;
474 use arrow_schema::Field;
475 use arrow_schema::FieldRef;
476 use arrow_schema::Fields;
477 use arrow_schema::Schema;
478 use rstest::fixture;
479 use rstest::rstest;
480 use vortex_array::dtype::DType;
481 use vortex_array::dtype::FieldName;
482 use vortex_array::dtype::FieldNames;
483 use vortex_array::dtype::Nullability;
484 use vortex_array::dtype::PType;
485 use vortex_array::dtype::StructFields;
486
487 use super::*;
488
489 #[test]
490 fn test_dtype_conversion_success() {
491 assert_eq!(DType::Null.to_arrow_dtype().unwrap(), DataType::Null);
492
493 assert_eq!(
494 DType::Bool(Nullability::NonNullable)
495 .to_arrow_dtype()
496 .unwrap(),
497 DataType::Boolean
498 );
499
500 assert_eq!(
501 DType::Primitive(PType::U64, Nullability::NonNullable)
502 .to_arrow_dtype()
503 .unwrap(),
504 DataType::UInt64
505 );
506
507 assert_eq!(
508 DType::Utf8(Nullability::NonNullable)
509 .to_arrow_dtype()
510 .unwrap(),
511 DataType::Utf8View
512 );
513
514 assert_eq!(
515 DType::Binary(Nullability::NonNullable)
516 .to_arrow_dtype()
517 .unwrap(),
518 DataType::BinaryView
519 );
520
521 assert_eq!(
522 DType::struct_(
523 [
524 ("field_a", DType::Bool(false.into())),
525 ("field_b", DType::Utf8(true.into()))
526 ],
527 Nullability::NonNullable,
528 )
529 .to_arrow_dtype()
530 .unwrap(),
531 DataType::Struct(Fields::from(vec![
532 FieldRef::from(Field::new("field_a", DataType::Boolean, false)),
533 FieldRef::from(Field::new("field_b", DataType::Utf8View, true)),
534 ]))
535 );
536 }
537
538 #[rstest]
539 #[case(1, DataType::Decimal128(1, 0))]
540 #[case(38, DataType::Decimal128(38, 0))]
541 #[case(39, DataType::Decimal256(39, 0))]
542 #[case(76, DataType::Decimal256(76, 0))]
543 fn test_decimal_dtype_to_arrow(#[case] precision: u8, #[case] expected: DataType) {
544 use vortex_array::dtype::DecimalDType;
545
546 let dtype = DType::Decimal(DecimalDType::new(precision, 0), Nullability::NonNullable);
547 assert_eq!(dtype.to_arrow_dtype().unwrap(), expected);
548 }
549
550 #[test]
551 fn test_variant_dtype_to_arrow_dtype_errors() {
552 let err = DType::Variant(Nullability::NonNullable)
553 .to_arrow_dtype()
554 .unwrap_err()
555 .to_string();
556 assert!(err.contains("Variant"));
557 }
558
559 #[test]
560 fn infer_nullable_list_element() {
561 let list_non_nullable = DType::List(
562 Arc::new(DType::Primitive(PType::I64, Nullability::NonNullable)),
563 Nullability::Nullable,
564 );
565
566 let arrow_list_non_nullable = list_non_nullable.to_arrow_dtype().unwrap();
567
568 let list_nullable = DType::List(
569 Arc::new(DType::Primitive(PType::I64, Nullability::Nullable)),
570 Nullability::Nullable,
571 );
572 let arrow_list_nullable = list_nullable.to_arrow_dtype().unwrap();
573
574 assert_ne!(arrow_list_non_nullable, arrow_list_nullable);
575 assert_eq!(
576 arrow_list_nullable,
577 DataType::List(Arc::new(Field::new_list_field(DataType::Int64, true))),
578 );
579 assert_eq!(
580 arrow_list_non_nullable,
581 DataType::List(Arc::new(Field::new_list_field(DataType::Int64, false))),
582 );
583 }
584
585 #[fixture]
586 fn the_struct() -> StructFields {
587 StructFields::new(
588 FieldNames::from([
589 FieldName::from("field_a"),
590 FieldName::from("field_b"),
591 FieldName::from("field_c"),
592 ]),
593 vec![
594 DType::Bool(Nullability::NonNullable),
595 DType::Utf8(Nullability::NonNullable),
596 DType::Primitive(PType::I32, Nullability::Nullable),
597 ],
598 )
599 }
600
601 #[rstest]
602 fn test_schema_conversion(the_struct: StructFields) {
603 let schema_nonnull = DType::Struct(the_struct, Nullability::NonNullable);
604
605 assert_eq!(
606 schema_nonnull.to_arrow_schema().unwrap(),
607 Schema::new(Fields::from(vec![
608 Field::new("field_a", DataType::Boolean, false),
609 Field::new("field_b", DataType::Utf8View, false),
610 Field::new("field_c", DataType::Int32, true),
611 ]))
612 );
613 }
614
615 #[test]
616 fn test_schema_variant_field_metadata() {
617 let dtype = DType::struct_(
618 [("v", DType::Variant(Nullability::NonNullable))],
619 Nullability::NonNullable,
620 );
621 let schema = dtype.to_arrow_schema().unwrap();
622 let field = schema.field(0);
623 assert_eq!(
624 field
625 .metadata()
626 .get("ARROW:extension:name")
627 .map(|s| s.as_str()),
628 Some("arrow.parquet.variant")
629 );
630 assert!(matches!(field.data_type(), DataType::Struct(_)));
631 assert!(!field.is_nullable());
632 }
633
634 #[rstest]
635 #[should_panic]
636 fn test_schema_conversion_panics(the_struct: StructFields) {
637 let schema_null = DType::Struct(the_struct, Nullability::Nullable);
638 schema_null.to_arrow_schema().unwrap();
639 }
640
641 #[test]
642 fn test_unicode_field_names_roundtrip() {
643 let unicode_field_name = "\u{5}=A";
648 let original_dtype = DType::struct_(
649 [(
650 unicode_field_name,
651 DType::Primitive(PType::I8, Nullability::Nullable),
652 )],
653 Nullability::NonNullable,
654 );
655
656 let arrow_dtype = original_dtype.to_arrow_dtype().unwrap();
657 let roundtripped_dtype = DType::from_arrow((&arrow_dtype, Nullability::NonNullable));
658
659 assert_eq!(original_dtype, roundtripped_dtype);
660 }
661
662 #[test]
663 fn test_unicode_field_names_nested_roundtrip() {
664 let inner_struct = DType::struct_(
668 [(
669 "\u{6}=inner",
670 DType::Primitive(PType::I32, Nullability::Nullable),
671 )],
672 Nullability::Nullable,
673 );
674 let original_dtype =
675 DType::struct_([("\u{7}=outer", inner_struct)], Nullability::NonNullable);
676
677 let arrow_dtype = original_dtype.to_arrow_dtype().unwrap();
678 let roundtripped_dtype = DType::from_arrow((&arrow_dtype, Nullability::NonNullable));
679
680 assert_eq!(original_dtype, roundtripped_dtype);
681 }
682
683 #[test]
684 fn map_dtype_roundtrip_uses_conventional_export_names_and_positional_import() -> VortexResult<()>
685 {
686 let dtype = DType::map(
687 DType::Primitive(PType::I32, Nullability::NonNullable),
688 DType::Utf8(Nullability::Nullable),
689 true,
690 Nullability::Nullable,
691 )?;
692
693 let arrow = dtype.to_arrow_dtype()?;
694 let DataType::Map(entries, keys_sorted) = &arrow else {
695 panic!("expected Map, got {arrow:?}");
696 };
697 assert!(*keys_sorted);
698 assert_eq!(entries.name(), "entries");
699 assert!(!entries.is_nullable());
700 let DataType::Struct(fields) = entries.data_type() else {
701 panic!("expected map entries to be a struct");
702 };
703 assert_eq!(fields[0].name(), "key");
704 assert!(!fields[0].is_nullable());
705 assert_eq!(fields[1].name(), "value");
706 assert!(fields[1].is_nullable());
707 assert_eq!(
708 DType::try_from_arrow((&arrow, Nullability::Nullable))?,
709 dtype
710 );
711
712 let positional = DataType::Map(
713 Arc::new(Field::new_struct(
714 "anything",
715 Fields::from(vec![
716 Field::new("first", DataType::Int32, false),
717 Field::new("second", DataType::Utf8, true),
718 ]),
719 false,
720 )),
721 false,
722 );
723 assert_eq!(
724 DType::try_from_arrow((&positional, Nullability::NonNullable))?,
725 DType::map(
726 DType::Primitive(PType::I32, Nullability::NonNullable),
727 DType::Utf8(Nullability::Nullable),
728 false,
729 Nullability::NonNullable,
730 )?
731 );
732
733 Ok(())
734 }
735
736 #[test]
737 fn map_dtype_import_rejects_invalid_arrow_shape() {
738 let invalid_entries = [
739 Field::new_struct(
740 "entries",
741 Fields::from(vec![
742 Field::new("key", DataType::Int32, false),
743 Field::new("value", DataType::Utf8, true),
744 ]),
745 true,
746 ),
747 Field::new("entries", DataType::Int32, false),
748 Field::new_struct(
749 "entries",
750 Fields::from(vec![Field::new("key", DataType::Int32, false)]),
751 false,
752 ),
753 Field::new_struct(
754 "entries",
755 Fields::from(vec![
756 Field::new("key", DataType::Int32, true),
757 Field::new("value", DataType::Utf8, true),
758 ]),
759 false,
760 ),
761 ];
762
763 for entries in invalid_entries {
764 let data_type = DataType::Map(Arc::new(entries), false);
765 assert!(DType::try_from_arrow((&data_type, Nullability::NonNullable)).is_err());
766 }
767 }
768
769 #[rstest]
772 #[case::duration(DataType::Duration(ArrowTimeUnit::Microsecond))]
773 #[case::interval(DataType::Interval(arrow_schema::IntervalUnit::DayTime))]
774 #[case::fixed_size_binary(DataType::FixedSizeBinary(3))]
775 fn test_try_from_arrow_unsupported_type_errors(#[case] data_type: DataType) {
776 let err = DType::try_from_arrow((&data_type, Nullability::NonNullable))
777 .expect_err("unsupported Arrow type should not convert")
778 .to_string();
779 assert!(err.contains("not supported"), "unexpected error: {err}");
780
781 let field = Field::new("c0", data_type, true);
783 assert!(DType::try_from_arrow(&field).is_err());
784 let schema = Schema::new(vec![field]);
785 assert!(DType::try_from_arrow(&schema).is_err());
786 }
787
788 #[test]
789 fn test_try_from_arrow_supported_type_succeeds() -> VortexResult<()> {
790 let dtype = DType::try_from_arrow((&DataType::Int32, Nullability::Nullable))?;
791 assert_eq!(dtype, DType::Primitive(PType::I32, Nullability::Nullable));
792 Ok(())
793 }
794}