Skip to main content

vortex_array/scalar/
arrow.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4//! Conversions between [`Scalar`] and Arrow scalar types.
5
6use std::sync::Arc;
7
8use arrow_array::Scalar as ArrowScalar;
9use arrow_array::*;
10use vortex_error::VortexError;
11use vortex_error::vortex_bail;
12use vortex_error::vortex_err;
13
14use crate::dtype::DType;
15use crate::dtype::PType;
16use crate::extension::datetime::AnyTemporal;
17use crate::extension::datetime::TemporalMetadata;
18use crate::extension::datetime::TimeUnit;
19use crate::scalar::BinaryScalar;
20use crate::scalar::BoolScalar;
21use crate::scalar::DecimalScalar;
22use crate::scalar::DecimalValue;
23use crate::scalar::ExtScalar;
24use crate::scalar::PrimitiveScalar;
25use crate::scalar::Scalar;
26use crate::scalar::Utf8Scalar;
27
28/// Arrow represents scalars as single-element arrays. This constant is the length of those arrays.
29const SCALAR_ARRAY_LEN: usize = 1;
30
31/// Converts an optional value to an Arrow scalar array.
32macro_rules! value_to_arrow_scalar {
33    ($V:expr, $AR:ty) => {
34        Ok(std::sync::Arc::new(
35            $V.map(<$AR>::new_scalar)
36                .unwrap_or_else(|| arrow_array::Scalar::new(<$AR>::new_null(SCALAR_ARRAY_LEN))),
37        ))
38    };
39}
40
41/// Converts an optional timestamp value to an Arrow scalar array.
42macro_rules! timestamp_to_arrow_scalar {
43    ($V:expr, $TZ:expr, $AR:ty) => {{
44        let array = match $V {
45            Some(v) => <$AR>::new_scalar(v).into_inner(),
46            None => <$AR>::new_null(SCALAR_ARRAY_LEN),
47        }
48        .with_timezone_opt($TZ);
49        Ok(Arc::new(ArrowScalar::new(array)))
50    }};
51}
52
53impl TryFrom<&Scalar> for Arc<dyn Datum> {
54    type Error = VortexError;
55
56    fn try_from(value: &Scalar) -> Result<Arc<dyn Datum>, Self::Error> {
57        match value.dtype() {
58            DType::Null => Ok(Arc::new(NullArray::new(SCALAR_ARRAY_LEN))),
59            DType::Bool(_) => bool_to_arrow(value.as_bool()),
60            DType::Primitive(..) => primitive_to_arrow(value.as_primitive()),
61            DType::Decimal(..) => decimal_to_arrow(value.as_decimal()),
62            DType::Utf8(_) => utf8_to_arrow(value.as_utf8()),
63            DType::Binary(_) => binary_to_arrow(value.as_binary()),
64            DType::List(..) => unimplemented!("list scalar conversion"),
65            DType::FixedSizeList(..) => unimplemented!("fixed-size list scalar conversion"),
66            DType::Struct(..) => unimplemented!("struct scalar conversion"),
67            DType::Union(..) => unimplemented!("union scalar conversion"),
68            DType::Variant(_) => unimplemented!("Variant scalar conversion"),
69            DType::Extension(..) => extension_to_arrow(value.as_extension()),
70        }
71    }
72}
73
74/// Convert a [`BoolScalar`] to an Arrow [`Datum`].
75fn bool_to_arrow(scalar: BoolScalar<'_>) -> Result<Arc<dyn Datum>, VortexError> {
76    value_to_arrow_scalar!(scalar.value(), BooleanArray)
77}
78
79/// Convert a [`PrimitiveScalar`] to an Arrow [`Datum`].
80fn primitive_to_arrow(scalar: PrimitiveScalar<'_>) -> Result<Arc<dyn Datum>, VortexError> {
81    match scalar.ptype() {
82        PType::U8 => value_to_arrow_scalar!(scalar.typed_value(), UInt8Array),
83        PType::U16 => value_to_arrow_scalar!(scalar.typed_value(), UInt16Array),
84        PType::U32 => value_to_arrow_scalar!(scalar.typed_value(), UInt32Array),
85        PType::U64 => value_to_arrow_scalar!(scalar.typed_value(), UInt64Array),
86        PType::I8 => value_to_arrow_scalar!(scalar.typed_value(), Int8Array),
87        PType::I16 => value_to_arrow_scalar!(scalar.typed_value(), Int16Array),
88        PType::I32 => value_to_arrow_scalar!(scalar.typed_value(), Int32Array),
89        PType::I64 => value_to_arrow_scalar!(scalar.typed_value(), Int64Array),
90        PType::F16 => value_to_arrow_scalar!(scalar.typed_value(), Float16Array),
91        PType::F32 => value_to_arrow_scalar!(scalar.typed_value(), Float32Array),
92        PType::F64 => value_to_arrow_scalar!(scalar.typed_value(), Float64Array),
93    }
94}
95
96/// Convert a [`DecimalScalar`] to an Arrow [`Datum`].
97fn decimal_to_arrow(scalar: DecimalScalar<'_>) -> Result<Arc<dyn Datum>, VortexError> {
98    // TODO(joe): Replace with decimal32, etc. once Arrow supports them.
99    match scalar.decimal_value() {
100        Some(DecimalValue::I8(v)) => Ok(Arc::new(Decimal128Array::new_scalar(v as i128))),
101        Some(DecimalValue::I16(v)) => Ok(Arc::new(Decimal128Array::new_scalar(v as i128))),
102        Some(DecimalValue::I32(v)) => Ok(Arc::new(Decimal128Array::new_scalar(v as i128))),
103        Some(DecimalValue::I64(v)) => Ok(Arc::new(Decimal128Array::new_scalar(v as i128))),
104        Some(DecimalValue::I128(v128)) => Ok(Arc::new(Decimal128Array::new_scalar(v128))),
105        Some(DecimalValue::I256(v256)) => Ok(Arc::new(Decimal256Array::new_scalar(v256.into()))),
106        None => Ok(Arc::new(arrow_array::Scalar::new(
107            Decimal128Array::new_null(SCALAR_ARRAY_LEN),
108        ))),
109    }
110}
111
112/// Convert a [`Utf8Scalar`] to an Arrow [`Datum`].
113fn utf8_to_arrow(scalar: Utf8Scalar<'_>) -> Result<Arc<dyn Datum>, VortexError> {
114    value_to_arrow_scalar!(scalar.value(), StringViewArray)
115}
116
117/// Convert a [`BinaryScalar`] to an Arrow [`Datum`].
118fn binary_to_arrow(scalar: BinaryScalar<'_>) -> Result<Arc<dyn Datum>, VortexError> {
119    value_to_arrow_scalar!(scalar.value(), BinaryViewArray)
120}
121
122/// Convert an [`ExtScalar`] to an Arrow [`Datum`].
123///
124/// Currently only temporal extension types (timestamps, dates, and times) are supported.
125fn extension_to_arrow(scalar: ExtScalar<'_>) -> Result<Arc<dyn Datum>, VortexError> {
126    let ext_dtype = scalar.ext_dtype();
127    let Some(temporal) = ext_dtype.metadata_opt::<AnyTemporal>() else {
128        vortex_bail!(
129            "Cannot convert extension scalar {} to Arrow",
130            ext_dtype.id()
131        )
132    };
133
134    let storage_scalar = scalar.to_storage_scalar();
135    let primitive = storage_scalar
136        .as_primitive_opt()
137        .ok_or_else(|| vortex_err!("Expected primitive scalar"))?;
138
139    match temporal {
140        TemporalMetadata::Timestamp(unit, tz) => {
141            let value = primitive.as_::<i64>();
142            match unit {
143                TimeUnit::Nanoseconds => {
144                    timestamp_to_arrow_scalar!(value, tz.clone(), TimestampNanosecondArray)
145                }
146                TimeUnit::Microseconds => {
147                    timestamp_to_arrow_scalar!(value, tz.clone(), TimestampMicrosecondArray)
148                }
149                TimeUnit::Milliseconds => {
150                    timestamp_to_arrow_scalar!(value, tz.clone(), TimestampMillisecondArray)
151                }
152                TimeUnit::Seconds => {
153                    timestamp_to_arrow_scalar!(value, tz.clone(), TimestampSecondArray)
154                }
155                TimeUnit::Days => {
156                    vortex_bail!("Unsupported TimeUnit {unit} for {}", ext_dtype.id())
157                }
158            }
159        }
160        TemporalMetadata::Date(unit) => match unit {
161            TimeUnit::Milliseconds => {
162                value_to_arrow_scalar!(primitive.as_::<i64>(), Date64Array)
163            }
164            TimeUnit::Days => {
165                value_to_arrow_scalar!(primitive.as_::<i32>(), Date32Array)
166            }
167            TimeUnit::Nanoseconds | TimeUnit::Microseconds | TimeUnit::Seconds => {
168                vortex_bail!("Unsupported TimeUnit {unit} for {}", ext_dtype.id())
169            }
170        },
171        TemporalMetadata::Time(unit) => match unit {
172            TimeUnit::Nanoseconds => {
173                value_to_arrow_scalar!(primitive.as_::<i64>(), Time64NanosecondArray)
174            }
175            TimeUnit::Microseconds => {
176                value_to_arrow_scalar!(primitive.as_::<i64>(), Time64MicrosecondArray)
177            }
178            TimeUnit::Milliseconds => {
179                value_to_arrow_scalar!(primitive.as_::<i32>(), Time32MillisecondArray)
180            }
181            TimeUnit::Seconds => {
182                value_to_arrow_scalar!(primitive.as_::<i32>(), Time32SecondArray)
183            }
184            TimeUnit::Days => {
185                vortex_bail!("Unsupported TimeUnit {unit} for {}", ext_dtype.id())
186            }
187        },
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    use std::sync::Arc;
194
195    use arrow_array::Datum;
196    use rstest::rstest;
197    use vortex_error::VortexResult;
198    use vortex_error::vortex_bail;
199
200    use crate::dtype::DType;
201    use crate::dtype::DecimalDType;
202    use crate::dtype::FieldDType;
203    use crate::dtype::NativeDType;
204    use crate::dtype::Nullability;
205    use crate::dtype::PType;
206    use crate::dtype::StructFields;
207    use crate::dtype::extension::ExtDType;
208    use crate::dtype::extension::ExtId;
209    use crate::dtype::extension::ExtVTable;
210    use crate::dtype::i256;
211    use crate::extension::datetime::Date;
212    use crate::extension::datetime::Time;
213    use crate::extension::datetime::TimeUnit;
214    use crate::extension::datetime::Timestamp;
215    use crate::extension::datetime::TimestampOptions;
216    use crate::scalar::DecimalValue;
217    use crate::scalar::Scalar;
218    use crate::scalar::ScalarValue;
219
220    #[test]
221    fn test_null_scalar_to_arrow() {
222        let scalar = Scalar::null(DType::Null);
223        let result = Arc::<dyn Datum>::try_from(&scalar);
224        assert!(result.is_ok());
225    }
226
227    #[test]
228    fn test_bool_scalar_to_arrow() {
229        let scalar = Scalar::bool(true, Nullability::NonNullable);
230        let result = Arc::<dyn Datum>::try_from(&scalar);
231        assert!(result.is_ok());
232    }
233
234    #[test]
235    fn test_null_bool_scalar_to_arrow() {
236        let scalar = Scalar::null(bool::dtype().as_nullable());
237        let result = Arc::<dyn Datum>::try_from(&scalar);
238        assert!(result.is_ok());
239    }
240
241    #[test]
242    fn test_primitive_u8_to_arrow() {
243        let scalar = Scalar::primitive(42u8, Nullability::NonNullable);
244        let result = Arc::<dyn Datum>::try_from(&scalar);
245        assert!(result.is_ok());
246    }
247
248    #[test]
249    fn test_primitive_u16_to_arrow() {
250        let scalar = Scalar::primitive(1000u16, Nullability::NonNullable);
251        let result = Arc::<dyn Datum>::try_from(&scalar);
252        assert!(result.is_ok());
253    }
254
255    #[test]
256    fn test_primitive_u32_to_arrow() {
257        let scalar = Scalar::primitive(100000u32, Nullability::NonNullable);
258        let result = Arc::<dyn Datum>::try_from(&scalar);
259        assert!(result.is_ok());
260    }
261
262    #[test]
263    fn test_primitive_u64_to_arrow() {
264        let scalar = Scalar::primitive(10000000000u64, Nullability::NonNullable);
265        let result = Arc::<dyn Datum>::try_from(&scalar);
266        assert!(result.is_ok());
267    }
268
269    #[test]
270    fn test_primitive_i8_to_arrow() {
271        let scalar = Scalar::primitive(-42i8, Nullability::NonNullable);
272        let result = Arc::<dyn Datum>::try_from(&scalar);
273        assert!(result.is_ok());
274    }
275
276    #[test]
277    fn test_primitive_i16_to_arrow() {
278        let scalar = Scalar::primitive(-1000i16, Nullability::NonNullable);
279        let result = Arc::<dyn Datum>::try_from(&scalar);
280        assert!(result.is_ok());
281    }
282
283    #[test]
284    fn test_primitive_i32_to_arrow() {
285        let scalar = Scalar::primitive(-100000i32, Nullability::NonNullable);
286        let result = Arc::<dyn Datum>::try_from(&scalar);
287        assert!(result.is_ok());
288    }
289
290    #[test]
291    fn test_primitive_i64_to_arrow() {
292        let scalar = Scalar::primitive(-10000000000i64, Nullability::NonNullable);
293        let result = Arc::<dyn Datum>::try_from(&scalar);
294        assert!(result.is_ok());
295    }
296
297    #[test]
298    fn test_primitive_f16_to_arrow() {
299        use crate::dtype::half::f16;
300
301        let scalar = Scalar::primitive(f16::from_f32(1.234), Nullability::NonNullable);
302        let result = Arc::<dyn Datum>::try_from(&scalar);
303        assert!(result.is_ok());
304    }
305
306    #[test]
307    fn test_primitive_f32_to_arrow() {
308        let scalar = Scalar::primitive(1.234f32, Nullability::NonNullable);
309        let result = Arc::<dyn Datum>::try_from(&scalar);
310        assert!(result.is_ok());
311    }
312
313    #[test]
314    fn test_primitive_f64_to_arrow() {
315        let scalar = Scalar::primitive(1.234567890123f64, Nullability::NonNullable);
316        let result = Arc::<dyn Datum>::try_from(&scalar);
317        assert!(result.is_ok());
318    }
319
320    #[test]
321    fn test_null_primitive_to_arrow() {
322        let scalar = Scalar::null(i32::dtype().as_nullable());
323        let result = Arc::<dyn Datum>::try_from(&scalar);
324        assert!(result.is_ok());
325    }
326
327    #[test]
328    fn test_utf8_scalar_to_arrow() {
329        let scalar = Scalar::utf8("hello world".to_string(), Nullability::NonNullable);
330        let result = Arc::<dyn Datum>::try_from(&scalar);
331        assert!(result.is_ok());
332    }
333
334    #[test]
335    fn test_null_utf8_scalar_to_arrow() {
336        let scalar = Scalar::null(String::dtype().as_nullable());
337        let result = Arc::<dyn Datum>::try_from(&scalar);
338        assert!(result.is_ok());
339    }
340
341    #[test]
342    fn test_binary_scalar_to_arrow() {
343        let data = vec![1u8, 2, 3, 4, 5];
344        let scalar = Scalar::binary(data, Nullability::NonNullable);
345        let result = Arc::<dyn Datum>::try_from(&scalar);
346        assert!(result.is_ok());
347    }
348
349    #[test]
350    fn test_null_binary_scalar_to_arrow() {
351        let scalar = Scalar::null(DType::Binary(Nullability::Nullable));
352        let result = Arc::<dyn Datum>::try_from(&scalar);
353        assert!(result.is_ok());
354    }
355
356    #[test]
357    fn test_decimal_scalars_to_arrow() {
358        // Test various decimal value types
359        let decimal_dtype = DecimalDType::new(5, 2);
360
361        let scalar_i8 = Scalar::decimal(
362            DecimalValue::I8(100),
363            decimal_dtype,
364            Nullability::NonNullable,
365        );
366        assert!(Arc::<dyn Datum>::try_from(&scalar_i8).is_ok());
367
368        let scalar_i16 = Scalar::decimal(
369            DecimalValue::I16(10000),
370            decimal_dtype,
371            Nullability::NonNullable,
372        );
373        assert!(Arc::<dyn Datum>::try_from(&scalar_i16).is_ok());
374
375        let scalar_i32 = Scalar::decimal(
376            DecimalValue::I32(99999),
377            decimal_dtype,
378            Nullability::NonNullable,
379        );
380        assert!(Arc::<dyn Datum>::try_from(&scalar_i32).is_ok());
381
382        let scalar_i64 = Scalar::decimal(
383            DecimalValue::I64(99999),
384            decimal_dtype,
385            Nullability::NonNullable,
386        );
387        assert!(Arc::<dyn Datum>::try_from(&scalar_i64).is_ok());
388
389        let scalar_i128 = Scalar::decimal(
390            DecimalValue::I128(99999),
391            decimal_dtype,
392            Nullability::NonNullable,
393        );
394        assert!(Arc::<dyn Datum>::try_from(&scalar_i128).is_ok());
395
396        // Test i256
397
398        let value_i256 = i256::from_i128(99999);
399        let scalar_i256 = Scalar::decimal(
400            DecimalValue::I256(value_i256),
401            decimal_dtype,
402            Nullability::NonNullable,
403        );
404        assert!(Arc::<dyn Datum>::try_from(&scalar_i256).is_ok());
405    }
406
407    #[test]
408    fn test_null_decimal_to_arrow() {
409        let decimal_dtype = DecimalDType::new(10, 2);
410        let scalar = Scalar::null(DType::Decimal(decimal_dtype, Nullability::Nullable));
411        let result = Arc::<dyn Datum>::try_from(&scalar);
412        assert!(result.is_ok());
413    }
414
415    #[test]
416    #[should_panic(expected = "struct scalar conversion")]
417    fn test_struct_scalar_to_arrow_todo() {
418        let struct_dtype = DType::Struct(
419            StructFields::from_iter([(
420                "field1",
421                FieldDType::from(DType::Primitive(PType::I32, Nullability::NonNullable)),
422            )]),
423            Nullability::NonNullable,
424        );
425
426        let struct_scalar = Scalar::struct_(
427            struct_dtype,
428            vec![Scalar::primitive(42i32, Nullability::NonNullable)],
429        );
430        Arc::<dyn Datum>::try_from(&struct_scalar).unwrap();
431    }
432
433    #[test]
434    #[should_panic(expected = "list scalar conversion")]
435    fn test_list_scalar_to_arrow_todo() {
436        let element_dtype = Arc::new(DType::Primitive(PType::I32, Nullability::NonNullable));
437        let list_scalar = Scalar::list(
438            element_dtype,
439            vec![
440                Scalar::primitive(1i32, Nullability::NonNullable),
441                Scalar::primitive(2i32, Nullability::NonNullable),
442            ],
443            Nullability::NonNullable,
444        );
445
446        Arc::<dyn Datum>::try_from(&list_scalar).unwrap();
447    }
448
449    #[test]
450    #[should_panic(expected = "Cannot convert extension scalar")]
451    fn test_non_temporal_extension_to_arrow_todo() {
452        #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
453        struct SomeExt;
454        impl ExtVTable for SomeExt {
455            type Metadata = String;
456            type NativeValue<'a> = &'a str;
457
458            #[expect(clippy::disallowed_methods, reason = "test-only id")]
459            fn id(&self) -> ExtId {
460                ExtId::new("some_ext")
461            }
462
463            fn serialize_metadata(&self, _options: &Self::Metadata) -> VortexResult<Vec<u8>> {
464                vortex_bail!("not implemented")
465            }
466
467            fn deserialize_metadata(&self, _data: &[u8]) -> VortexResult<Self::Metadata> {
468                vortex_bail!("not implemented")
469            }
470
471            fn validate_dtype(_ext_dtype: &ExtDType<Self>) -> VortexResult<()> {
472                Ok(())
473            }
474
475            fn unpack_native<'a>(
476                _ext_dtype: &'a ExtDType<Self>,
477                _storage_value: &'a ScalarValue,
478            ) -> VortexResult<Self::NativeValue<'a>> {
479                Ok("")
480            }
481        }
482
483        let scalar = Scalar::extension::<SomeExt>(
484            "".into(),
485            Scalar::primitive(42i32, Nullability::NonNullable),
486        );
487
488        Arc::<dyn Datum>::try_from(&scalar).unwrap();
489    }
490
491    #[rstest]
492    #[case(TimeUnit::Nanoseconds, PType::I64, 123456789i64)]
493    #[case(TimeUnit::Microseconds, PType::I64, 123456789i64)]
494    #[case(TimeUnit::Milliseconds, PType::I32, 123456i64)]
495    #[case(TimeUnit::Seconds, PType::I32, 1234i64)]
496    fn test_temporal_time_to_arrow(
497        #[case] time_unit: TimeUnit,
498        #[case] ptype: PType,
499        #[case] value: i64,
500    ) {
501        let scalar = Scalar::extension::<Time>(
502            time_unit,
503            match ptype {
504                PType::I32 => {
505                    let i32_value = i32::try_from(value).expect("test value should fit in i32");
506                    Scalar::primitive(i32_value, Nullability::NonNullable)
507                }
508                PType::I64 => Scalar::primitive(value, Nullability::NonNullable),
509                _ => unreachable!(),
510            },
511        );
512
513        let result = Arc::<dyn Datum>::try_from(&scalar);
514        assert!(result.is_ok());
515    }
516
517    #[rstest]
518    #[case(TimeUnit::Milliseconds, PType::I64, 1234567890000i64)]
519    #[case(TimeUnit::Days, PType::I32, 19000i64)]
520    fn test_temporal_date_to_arrow(
521        #[case] time_unit: TimeUnit,
522        #[case] ptype: PType,
523        #[case] value: i64,
524    ) {
525        let scalar = Scalar::extension::<Date>(
526            time_unit,
527            match ptype {
528                PType::I32 => {
529                    let i32_value = i32::try_from(value).expect("test value should fit in i32");
530                    Scalar::primitive(i32_value, Nullability::NonNullable)
531                }
532                PType::I64 => Scalar::primitive(value, Nullability::NonNullable),
533                _ => unreachable!(),
534            },
535        );
536
537        let result = Arc::<dyn Datum>::try_from(&scalar);
538        assert!(result.is_ok());
539    }
540
541    #[rstest]
542    #[case(TimeUnit::Nanoseconds, 1234567890000000000i64)]
543    #[case(TimeUnit::Microseconds, 1234567890000000i64)]
544    #[case(TimeUnit::Milliseconds, 1234567890000i64)]
545    #[case(TimeUnit::Seconds, 1234567890i64)]
546    fn test_temporal_timestamp_to_arrow(#[case] time_unit: TimeUnit, #[case] value: i64) {
547        let scalar = Scalar::extension::<Timestamp>(
548            TimestampOptions {
549                unit: time_unit,
550                tz: None,
551            },
552            Scalar::primitive(value, Nullability::NonNullable),
553        );
554
555        let result = Arc::<dyn Datum>::try_from(&scalar);
556        assert!(result.is_ok());
557    }
558
559    #[rstest]
560    #[case(TimeUnit::Nanoseconds, "UTC", 1234567890000000000i64)]
561    #[case(TimeUnit::Microseconds, "America/New_York", 1234567890000000i64)]
562    #[case(TimeUnit::Microseconds, "Asia/Qatar", 1234567890000000i64)]
563    #[case(TimeUnit::Microseconds, "Australia/Sydney", 1234567890000000i64)]
564    #[case(TimeUnit::Milliseconds, "Pacific/Honolulu", 1234567890000i64)]
565    #[case(TimeUnit::Seconds, "GMT", 1234567890i64)]
566    fn test_temporal_timestamp_tz_to_arrow(
567        #[case] time_unit: TimeUnit,
568        #[case] tz: &str,
569        #[case] value: i64,
570    ) {
571        let scalar = Scalar::extension::<Timestamp>(
572            TimestampOptions {
573                unit: time_unit,
574                tz: Some(tz.into()),
575            },
576            Scalar::primitive(value, Nullability::NonNullable),
577        );
578
579        let result = Arc::<dyn Datum>::try_from(&scalar);
580        assert!(result.is_ok());
581    }
582
583    #[test]
584    fn test_temporal_with_null_value() {
585        let scalar = Scalar::extension::<Time>(
586            TimeUnit::Milliseconds,
587            Scalar::null(DType::Primitive(PType::I32, Nullability::Nullable)),
588        );
589
590        let _result = Arc::<dyn Datum>::try_from(&scalar).unwrap();
591    }
592
593    #[test]
594    #[should_panic(expected = "DType utf8 is not a primitive type")]
595    fn test_temporal_non_primitive_storage_error() {
596        let _scalar = Scalar::extension::<Time>(
597            TimeUnit::Nanoseconds,
598            Scalar::utf8("not a timestamp", Nullability::NonNullable),
599        );
600    }
601}