Skip to main content

parquet_variant_compute/
type_conversion.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: See the NOTICE file distributed with this work for additional information regarding copyright ownership
3// SPDX-FileContributor: Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements
4
5//! Module for transforming a typed arrow `Array` to `VariantArray`.
6
7use arrow::array::ArrowNativeTypeOp;
8use arrow::compute::kernels::cast_utils::parse_decimal;
9use arrow::compute::{
10    CastOptions, DecimalCast, cast_num_to_bool, cast_single_string_to_boolean_default,
11    float_to_decimal_opt, num_cast, rescale_decimal, single_bool_to_numeric,
12    single_decimal_to_float_lossy,
13};
14use arrow::datatypes::{
15    self, ArrowPrimitiveType, ArrowTimestampType, Decimal32Type, Decimal64Type, Decimal128Type,
16    Decimal256Type, DecimalType,
17};
18use arrow::error::{ArrowError, Result};
19use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Timelike, Utc};
20use half::f16;
21use num_traits::NumCast;
22use parquet_variant::{Variant, VariantDecimal4, VariantDecimal8, VariantDecimal16};
23
24/// Controls the conversions allowed when extracting a typed value from a variant.
25#[derive(Debug, Clone, Copy)]
26pub(crate) enum VariantCastMode {
27    /// Use the conversions permitted by the variant shredding specification.
28    Shred,
29    /// Allow casts when extracting a value with `variant_get`.
30    Get,
31}
32
33/// Extension trait for Arrow primitive types that can extract their native value from a Variant
34pub(crate) trait PrimitiveFromVariant: ArrowPrimitiveType {
35    fn from_variant(variant: &Variant<'_, '_>, cast_mode: VariantCastMode) -> Option<Self::Native>;
36}
37
38/// Extension trait for Arrow timestamp types that can extract their native value from a Variant
39/// We can't use [`PrimitiveFromVariant`] directly because we need _two_ implementations for each
40/// timestamp type -- the `NTZ` param here.
41pub(crate) trait TimestampFromVariant<const NTZ: bool>: ArrowTimestampType {
42    fn from_variant(variant: &Variant<'_, '_>, cast_mode: VariantCastMode) -> Option<Self::Native>;
43}
44
45/// Cast a single `Variant` value with safe/strict semantics.
46///
47/// Returns `Ok(Some(_))` on successful conversion.
48/// Returns `Ok(None)` when conversion fails in safe mode or the source value is `Variant::Null`.
49/// Returns `Err(_)` when conversion fails in strict mode.
50pub(crate) fn variant_cast_with_options<'a, 'm, 'v, T>(
51    variant: &'a Variant<'m, 'v>,
52    cast_options: &CastOptions<'_>,
53    cast: impl FnOnce(&'a Variant<'m, 'v>) -> Option<T>,
54) -> Result<Option<T>> {
55    if let Some(value) = cast(variant) {
56        Ok(Some(value))
57    } else if matches!(variant, Variant::Null) || cast_options.safe {
58        Ok(None)
59    } else {
60        Err(ArrowError::CastError(format!(
61            "Failed to cast variant value {variant:?}"
62        )))
63    }
64}
65
66/// Macro to generate PrimitiveFromVariant implementations for Arrow primitive types
67macro_rules! impl_primitive_from_variant {
68    ($arrow_type:ty, $shred_fun:expr, $get_method:ident $(, $cast_fn:expr)?) => {
69        impl PrimitiveFromVariant for $arrow_type {
70            fn from_variant(variant: &Variant<'_, '_>, cast_mode: VariantCastMode) -> Option<Self::Native> {
71                let value = match cast_mode {
72                    VariantCastMode::Shred => $shred_fun(variant),
73                    VariantCastMode::Get => $get_method(variant),
74                };
75                $( let value = value.and_then($cast_fn); )?
76                value
77            }
78        }
79    };
80}
81
82macro_rules! impl_timestamp_from_variant {
83    ($timestamp_type:ty, $shred_fun:expr, $variant_method:expr, ntz=$ntz:ident, $cast_fn:expr $(,)?) => {
84        impl TimestampFromVariant<{ $ntz }> for $timestamp_type {
85            fn from_variant(
86                variant: &Variant<'_, '_>,
87                cast_mode: VariantCastMode,
88            ) -> Option<Self::Native> {
89                let value = match cast_mode {
90                    VariantCastMode::Shred => ($shred_fun)(variant),
91                    VariantCastMode::Get => $variant_method(variant),
92                };
93
94                value.and_then($cast_fn)
95            }
96        }
97    };
98}
99
100fn convert_to_timestamp_nano(value: &Variant) -> Option<DateTime<Utc>> {
101    match *value {
102        Variant::TimestampNanos(d) | Variant::TimestampMicros(d) => Some(d),
103        _ => None,
104    }
105}
106
107fn convert_to_timestamp_ntz_nano(value: &Variant) -> Option<NaiveDateTime> {
108    match *value {
109        Variant::TimestampNtzNanos(d) | Variant::TimestampNtzMicros(d) => Some(d),
110        _ => None,
111    }
112}
113
114enum NumericKind {
115    Integer,
116    Float,
117}
118
119trait DecimalCastTarget: NumCast + Default {
120    const KIND: NumericKind;
121}
122
123macro_rules! impl_decimal_cast_target {
124    ($raw_type: ident, $target_kind:expr) => {
125        impl DecimalCastTarget for $raw_type {
126            const KIND: NumericKind = $target_kind;
127        }
128    };
129}
130
131impl_decimal_cast_target!(i8, NumericKind::Integer);
132impl_decimal_cast_target!(i16, NumericKind::Integer);
133impl_decimal_cast_target!(i32, NumericKind::Integer);
134impl_decimal_cast_target!(i64, NumericKind::Integer);
135impl_decimal_cast_target!(u8, NumericKind::Integer);
136impl_decimal_cast_target!(u16, NumericKind::Integer);
137impl_decimal_cast_target!(u32, NumericKind::Integer);
138impl_decimal_cast_target!(u64, NumericKind::Integer);
139impl_decimal_cast_target!(f16, NumericKind::Float);
140impl_decimal_cast_target!(f32, NumericKind::Float);
141impl_decimal_cast_target!(f64, NumericKind::Float);
142
143/// Converts a boolean or numeric variant(integers, floating-point, and decimals)
144/// to the specified numeric type `T`.
145///
146/// Uses Arrow's casting logic to perform the conversion. Returns `Some(T)` if
147/// the conversion succeeds, `None` if the variant can't be casted to type `T`.
148fn as_num<T>(variant: &Variant) -> Option<T>
149where
150    T: DecimalCastTarget,
151{
152    match *variant {
153        Variant::BooleanFalse => single_bool_to_numeric(false),
154        Variant::BooleanTrue => single_bool_to_numeric(true),
155        Variant::Int8(i) => num_cast(i),
156        Variant::Int16(i) => num_cast(i),
157        Variant::Int32(i) => num_cast(i),
158        Variant::Int64(i) => num_cast(i),
159        Variant::Float(f) => num_cast(f),
160        Variant::Double(d) => num_cast(d),
161        Variant::Decimal4(d) => {
162            cast_decimal_to_num::<Decimal32Type, T, _>(d.integer(), d.scale(), |x| x as f64)
163        }
164        Variant::Decimal8(d) => {
165            cast_decimal_to_num::<Decimal64Type, T, _>(d.integer(), d.scale(), |x| x as f64)
166        }
167        Variant::Decimal16(d) => {
168            cast_decimal_to_num::<Decimal128Type, T, _>(d.integer(), d.scale(), |x| x as f64)
169        }
170        _ => None,
171    }
172}
173
174fn cast_decimal_to_num<D, T, F>(raw: D::Native, scale: u8, as_float: F) -> Option<T>
175where
176    D: DecimalType,
177    D::Native: NumCast + ArrowNativeTypeOp,
178    T: DecimalCastTarget,
179    F: Fn(D::Native) -> f64,
180{
181    let base: D::Native = NumCast::from(10)?;
182
183    let div = base.pow_checked(<u32 as From<u8>>::from(scale)).ok()?;
184    match T::KIND {
185        NumericKind::Integer => raw
186            .div_checked(div)
187            .ok()
188            .and_then(<T as NumCast>::from::<D::Native>),
189        NumericKind::Float => T::from(single_decimal_to_float_lossy::<D, _>(
190            &as_float,
191            raw,
192            <i32 as From<u8>>::from(scale),
193        )),
194    }
195}
196
197fn cast_naive_date(value: &Variant<'_, '_>) -> Option<NaiveDate> {
198    value.as_naive_date()
199}
200
201fn cast_time_utc(value: &Variant<'_, '_>) -> Option<NaiveTime> {
202    value.as_time_utc()
203}
204
205// helper function for the types that would never be the shred target type.
206fn always_none<T>(_input: &Variant) -> Option<T> {
207    None
208}
209
210impl_primitive_from_variant!(datatypes::Int32Type, Variant::as_int32, as_num);
211impl_primitive_from_variant!(datatypes::Int16Type, Variant::as_int16, as_num);
212impl_primitive_from_variant!(datatypes::Int8Type, Variant::as_int8, as_num);
213impl_primitive_from_variant!(datatypes::Int64Type, Variant::as_int64, as_num);
214impl_primitive_from_variant!(datatypes::UInt8Type, always_none, as_num);
215impl_primitive_from_variant!(datatypes::UInt16Type, always_none, as_num);
216impl_primitive_from_variant!(datatypes::UInt32Type, always_none, as_num);
217impl_primitive_from_variant!(datatypes::UInt64Type, always_none, as_num);
218impl_primitive_from_variant!(datatypes::Float16Type, always_none, as_num);
219impl_primitive_from_variant!(datatypes::Float32Type, Variant::as_f32, as_num);
220impl_primitive_from_variant!(datatypes::Float64Type, Variant::as_f64, as_num);
221impl_primitive_from_variant!(
222    datatypes::Date32Type,
223    Variant::as_naive_date,
224    cast_naive_date,
225    |v| { Some(datatypes::Date32Type::from_naive_date(v)) }
226);
227impl_primitive_from_variant!(
228    datatypes::Date64Type,
229    Variant::as_naive_date,
230    cast_naive_date,
231    |v| { Some(datatypes::Date64Type::from_naive_date(v)) }
232);
233impl_primitive_from_variant!(
234    datatypes::Time32SecondType,
235    always_none, // would never shred to Time32SecondType
236    cast_time_utc,
237    |v| {
238        // Return None if there are leftover nanoseconds
239        if v.nanosecond() != 0 {
240            None
241        } else {
242            Some(v.num_seconds_from_midnight() as i32)
243        }
244    }
245);
246impl_primitive_from_variant!(
247    datatypes::Time32MillisecondType,
248    always_none, // would never shred to Time32MillisecondType
249    cast_time_utc,
250    |v| {
251        // Return None if there are leftover microseconds
252        if v.nanosecond() % 1_000_000 != 0 {
253            None
254        } else {
255            Some(
256                (v.num_seconds_from_midnight() * 1_000) as i32
257                    + (v.nanosecond() / 1_000_000) as i32,
258            )
259        }
260    }
261);
262impl_primitive_from_variant!(
263    datatypes::Time64MicrosecondType,
264    Variant::as_time_utc,
265    cast_time_utc,
266    |v| { Some(v.num_seconds_from_midnight() as i64 * 1_000_000 + v.nanosecond() as i64 / 1_000) }
267);
268impl_primitive_from_variant!(
269    datatypes::Time64NanosecondType,
270    always_none, // would never shred to Time64NanosecondType
271    cast_time_utc,
272    |v| {
273        // convert micro to nano seconds
274        Some(v.num_seconds_from_midnight() as i64 * 1_000_000_000 + v.nanosecond() as i64)
275    }
276);
277impl_timestamp_from_variant!(
278    datatypes::TimestampSecondType,
279    always_none, // would never shred to TimestampSecondType
280    convert_to_timestamp_ntz_nano,
281    ntz = true,
282    |timestamp| {
283        // Return None if there are leftover nanoseconds
284        if timestamp.nanosecond() != 0 {
285            None
286        } else {
287            Self::from_naive_datetime(timestamp, None)
288        }
289    }
290);
291impl_timestamp_from_variant!(
292    datatypes::TimestampSecondType,
293    always_none, // would never shred to TimestampSecondType
294    convert_to_timestamp_nano,
295    ntz = false,
296    |timestamp| {
297        // Return None if there are leftover nanoseconds
298        if timestamp.nanosecond() != 0 {
299            None
300        } else {
301            Self::from_naive_datetime(timestamp.naive_utc(), None)
302        }
303    }
304);
305impl_timestamp_from_variant!(
306    datatypes::TimestampMillisecondType,
307    always_none, // would never shred to TimestampMillisecondType
308    convert_to_timestamp_ntz_nano,
309    ntz = true,
310    |timestamp| {
311        // Return None if there are leftover microseconds
312        if timestamp.nanosecond() % 1_000_000 != 0 {
313            None
314        } else {
315            Self::from_naive_datetime(timestamp, None)
316        }
317    }
318);
319impl_timestamp_from_variant!(
320    datatypes::TimestampMillisecondType,
321    always_none, // would never shred to TimestampMillisecondType
322    convert_to_timestamp_nano,
323    ntz = false,
324    |timestamp| {
325        // Return None if there are leftover microseconds
326        if timestamp.nanosecond() % 1_000_000 != 0 {
327            None
328        } else {
329            Self::from_naive_datetime(timestamp.naive_utc(), None)
330        }
331    }
332);
333impl_timestamp_from_variant!(
334    datatypes::TimestampMicrosecondType,
335    Variant::as_timestamp_ntz_micros,
336    Variant::as_timestamp_ntz_micros,
337    ntz = true,
338    |timestamp| Self::from_naive_datetime(timestamp, None),
339);
340impl_timestamp_from_variant!(
341    datatypes::TimestampMicrosecondType,
342    Variant::as_timestamp_micros,
343    Variant::as_timestamp_micros,
344    ntz = false,
345    |timestamp| Self::from_naive_datetime(timestamp.naive_utc(), None)
346);
347impl_timestamp_from_variant!(
348    datatypes::TimestampNanosecondType,
349    Variant::as_timestamp_ntz_nanos,
350    convert_to_timestamp_ntz_nano,
351    ntz = true,
352    |timestamp| Self::from_naive_datetime(timestamp, None)
353);
354impl_timestamp_from_variant!(
355    datatypes::TimestampNanosecondType,
356    Variant::as_timestamp_nanos,
357    convert_to_timestamp_nano,
358    ntz = false,
359    |timestamp| Self::from_naive_datetime(timestamp.naive_utc(), None)
360);
361
362/// Returns the unscaled integer representation for Arrow decimal type `O`
363/// from a `Variant`.
364///
365/// - `precision` and `scale` specify the target Arrow decimal parameters
366/// - Integer variants (`Int8/16/32/64`) are treated as decimals with scale 0
367/// - Floating point variants (`Float/Double`) are converted to decimals with the given scale
368/// - String variants (`String/ShortString`) are parsed as decimals with the given scale
369/// - Decimal variants (`Decimal4/8/16`) use their embedded precision and scale
370///
371/// The value is rescaled to (`precision`, `scale`) using `rescale_decimal` for integers,
372/// `float_to_decimal_opt` for floats, and `parse_decimal` for strings.
373/// returns `None` if it cannot fit the requested precision.
374pub(crate) fn variant_to_unscaled_decimal<O>(
375    variant: &Variant<'_, '_>,
376    precision: u8,
377    scale: i8,
378) -> Option<O::Native>
379where
380    O: DecimalType,
381    O::Native: DecimalCast,
382{
383    match variant {
384        Variant::Int8(i) => rescale_decimal::<Decimal32Type, O>(
385            *i as i32,
386            VariantDecimal4::MAX_PRECISION,
387            0,
388            precision,
389            scale,
390        ),
391        Variant::Int16(i) => rescale_decimal::<Decimal32Type, O>(
392            *i as i32,
393            VariantDecimal4::MAX_PRECISION,
394            0,
395            precision,
396            scale,
397        ),
398        Variant::Int32(i) => rescale_decimal::<Decimal32Type, O>(
399            *i,
400            VariantDecimal4::MAX_PRECISION,
401            0,
402            precision,
403            scale,
404        ),
405        Variant::Int64(i) => rescale_decimal::<Decimal64Type, O>(
406            *i,
407            VariantDecimal8::MAX_PRECISION,
408            0,
409            precision,
410            scale,
411        ),
412        Variant::Float(f) => {
413            float_to_decimal_opt::<O>(<f64 as From<f32>>::from(*f), precision, scale)
414        }
415        Variant::Double(f) => float_to_decimal_opt::<O>(*f, precision, scale),
416        Variant::String(v) => parse_decimal::<O>(v, precision, scale).ok(),
417        Variant::ShortString(v) => parse_decimal::<O>(v, precision, scale).ok(),
418        Variant::Decimal4(d) => rescale_decimal::<Decimal32Type, O>(
419            d.integer(),
420            VariantDecimal4::MAX_PRECISION,
421            d.scale() as i8,
422            precision,
423            scale,
424        ),
425        Variant::Decimal8(d) => rescale_decimal::<Decimal64Type, O>(
426            d.integer(),
427            VariantDecimal8::MAX_PRECISION,
428            d.scale() as i8,
429            precision,
430            scale,
431        ),
432        Variant::Decimal16(d) => rescale_decimal::<Decimal128Type, O>(
433            d.integer(),
434            VariantDecimal16::MAX_PRECISION,
435            d.scale() as i8,
436            precision,
437            scale,
438        ),
439        _ => None,
440    }
441}
442
443/// Returns the unscaled integer representation for Arrow decimal type `O` from a `Variant`.
444///
445/// Unlike `variant_to_unscaled_decimal`, this function only accepts integer and decimal
446/// variants. Decimal values may be rescaled only when the conversion is exact, as verified
447/// by converting the result back to the original scale.
448pub(crate) fn shred_variant_to_unscaled_decimal<O>(
449    variant: &Variant<'_, '_>,
450    precision: u8,
451    scale: i8,
452) -> Option<O::Native>
453where
454    O: ShredDecimalVariant,
455    O::Native: DecimalCast,
456{
457    match variant {
458        Variant::Int8(_)
459        | Variant::Int16(_)
460        | Variant::Int32(_)
461        | Variant::Int64(_)
462        | Variant::Decimal4(_)
463        | Variant::Decimal8(_)
464        | Variant::Decimal16(_) => O::shred_variant(variant, precision, scale),
465        _ => None,
466    }
467}
468pub(crate) trait ShredDecimalVariant: DecimalType {
469    fn shred_variant(value: &Variant<'_, '_>, precision: u8, scale: i8) -> Option<Self::Native>;
470}
471
472fn convert_to_unscaled_decimal<I, O>(
473    input: I::Native,
474    input_precision: u8,
475    input_scale: i8,
476    target_precision: u8,
477    target_scale: i8,
478) -> Option<O::Native>
479where
480    I: DecimalType,
481    O: DecimalType,
482    I::Native: DecimalCast,
483    O::Native: DecimalCast,
484{
485    let converted = rescale_decimal::<I, O>(
486        input,
487        input_precision,
488        input_scale,
489        target_precision,
490        target_scale,
491    )?;
492
493    let converted_back = rescale_decimal::<O, I>(
494        converted,
495        target_precision,
496        target_scale,
497        input_precision,
498        input_scale,
499    )?;
500    if converted_back == input {
501        return Some(converted);
502    }
503
504    None
505}
506
507impl ShredDecimalVariant for Decimal32Type {
508    fn shred_variant(value: &Variant<'_, '_>, precision: u8, scale: i8) -> Option<Self::Native> {
509        match *value {
510            Variant::Int8(i) => convert_to_unscaled_decimal::<Decimal32Type, Decimal32Type>(
511                i as i32,
512                VariantDecimal4::MAX_PRECISION,
513                0,
514                precision,
515                scale,
516            ),
517            Variant::Int16(i) => convert_to_unscaled_decimal::<Decimal32Type, Decimal32Type>(
518                i as i32,
519                VariantDecimal4::MAX_PRECISION,
520                0,
521                precision,
522                scale,
523            ),
524            Variant::Int32(i) => convert_to_unscaled_decimal::<Decimal32Type, Decimal32Type>(
525                i,
526                VariantDecimal4::MAX_PRECISION,
527                0,
528                precision,
529                scale,
530            ),
531            Variant::Int64(i) => {
532                let i32_value = <i64 as TryInto<i32>>::try_into(i).ok()?;
533                convert_to_unscaled_decimal::<Decimal32Type, Decimal32Type>(
534                    i32_value,
535                    VariantDecimal4::MAX_PRECISION,
536                    0,
537                    precision,
538                    scale,
539                )
540            }
541            Variant::Decimal4(d) => convert_to_unscaled_decimal::<Decimal32Type, Decimal32Type>(
542                d.integer(),
543                VariantDecimal4::MAX_PRECISION,
544                d.scale() as i8,
545                precision,
546                scale,
547            ),
548            Variant::Decimal8(d) => convert_to_unscaled_decimal::<Decimal64Type, Decimal32Type>(
549                d.integer(),
550                VariantDecimal8::MAX_PRECISION,
551                d.scale() as i8,
552                precision,
553                scale,
554            ),
555            Variant::Decimal16(d) => convert_to_unscaled_decimal::<Decimal128Type, Decimal32Type>(
556                d.integer(),
557                VariantDecimal16::MAX_PRECISION,
558                d.scale() as i8,
559                precision,
560                scale,
561            ),
562            _ => None,
563        }
564    }
565}
566
567impl ShredDecimalVariant for Decimal64Type {
568    fn shred_variant(value: &Variant<'_, '_>, precision: u8, scale: i8) -> Option<Self::Native> {
569        match *value {
570            Variant::Int8(i) => convert_to_unscaled_decimal::<Decimal64Type, Decimal64Type>(
571                i as i64,
572                VariantDecimal8::MAX_PRECISION,
573                0,
574                precision,
575                scale,
576            ),
577            Variant::Int16(i) => convert_to_unscaled_decimal::<Decimal64Type, Decimal64Type>(
578                i as i64,
579                VariantDecimal8::MAX_PRECISION,
580                0,
581                precision,
582                scale,
583            ),
584            Variant::Int32(i) => convert_to_unscaled_decimal::<Decimal64Type, Decimal64Type>(
585                i as i64,
586                VariantDecimal8::MAX_PRECISION,
587                0,
588                precision,
589                scale,
590            ),
591            Variant::Int64(i) => convert_to_unscaled_decimal::<Decimal64Type, Decimal64Type>(
592                i,
593                VariantDecimal8::MAX_PRECISION,
594                0,
595                precision,
596                scale,
597            ),
598            Variant::Decimal4(d) => convert_to_unscaled_decimal::<Decimal32Type, Decimal64Type>(
599                d.integer(),
600                VariantDecimal4::MAX_PRECISION,
601                d.scale() as i8,
602                precision,
603                scale,
604            ),
605            Variant::Decimal8(d) => convert_to_unscaled_decimal::<Decimal64Type, Decimal64Type>(
606                d.integer(),
607                VariantDecimal8::MAX_PRECISION,
608                d.scale() as i8,
609                precision,
610                scale,
611            ),
612            Variant::Decimal16(d) => convert_to_unscaled_decimal::<Decimal128Type, Decimal64Type>(
613                d.integer(),
614                VariantDecimal16::MAX_PRECISION,
615                d.scale() as i8,
616                precision,
617                scale,
618            ),
619            _ => None,
620        }
621    }
622}
623
624impl ShredDecimalVariant for Decimal128Type {
625    fn shred_variant(value: &Variant<'_, '_>, precision: u8, scale: i8) -> Option<Self::Native> {
626        match *value {
627            Variant::Int8(i) => convert_to_unscaled_decimal::<Decimal128Type, Decimal128Type>(
628                i as i128,
629                VariantDecimal4::MAX_PRECISION,
630                0,
631                precision,
632                scale,
633            ),
634            Variant::Int16(i) => convert_to_unscaled_decimal::<Decimal128Type, Decimal128Type>(
635                i as i128,
636                VariantDecimal4::MAX_PRECISION,
637                0,
638                precision,
639                scale,
640            ),
641            Variant::Int32(i) => convert_to_unscaled_decimal::<Decimal128Type, Decimal128Type>(
642                i as i128,
643                VariantDecimal4::MAX_PRECISION,
644                0,
645                precision,
646                scale,
647            ),
648            Variant::Int64(i) => convert_to_unscaled_decimal::<Decimal128Type, Decimal128Type>(
649                i as i128,
650                VariantDecimal4::MAX_PRECISION,
651                0,
652                precision,
653                scale,
654            ),
655            Variant::Decimal4(d) => convert_to_unscaled_decimal::<Decimal32Type, Decimal128Type>(
656                d.integer(),
657                VariantDecimal4::MAX_PRECISION,
658                d.scale() as i8,
659                precision,
660                scale,
661            ),
662            Variant::Decimal8(d) => convert_to_unscaled_decimal::<Decimal64Type, Decimal128Type>(
663                d.integer(),
664                VariantDecimal8::MAX_PRECISION,
665                d.scale() as i8,
666                precision,
667                scale,
668            ),
669            Variant::Decimal16(d) => convert_to_unscaled_decimal::<Decimal128Type, Decimal128Type>(
670                d.integer(),
671                VariantDecimal16::MAX_PRECISION,
672                d.scale() as i8,
673                precision,
674                scale,
675            ),
676            _ => None,
677        }
678    }
679}
680
681impl ShredDecimalVariant for Decimal256Type {
682    fn shred_variant(_value: &Variant<'_, '_>, _precision: u8, _scale: i8) -> Option<Self::Native> {
683        None // always return none because we'll never shred to decimal256
684    }
685}
686
687pub(crate) fn variant_to_boolean(
688    variant: &Variant<'_, '_>,
689    cast_mode: VariantCastMode,
690) -> Option<bool> {
691    if matches!(cast_mode, VariantCastMode::Shred) {
692        return variant.as_boolean();
693    }
694
695    match variant {
696        Variant::BooleanTrue => Some(true),
697        Variant::BooleanFalse => Some(false),
698        Variant::Int8(i) => Some(cast_num_to_bool(*i)),
699        Variant::Int16(i) => Some(cast_num_to_bool(*i)),
700        Variant::Int32(i) => Some(cast_num_to_bool(*i)),
701        Variant::Int64(i) => Some(cast_num_to_bool(*i)),
702        Variant::Float(f) => Some(cast_num_to_bool(*f)),
703        Variant::Double(d) => Some(cast_num_to_bool(*d)),
704        Variant::ShortString(s) => cast_single_string_to_boolean_default(s.as_str()),
705        Variant::String(s) => cast_single_string_to_boolean_default(s),
706        _ => None,
707    }
708}
709
710/// Convert the value at a specific index in the given array into a `Variant`.
711macro_rules! non_generic_conversion_single_value {
712    ($array:expr, $cast_fn:expr, $index:expr) => {{
713        let array = $array;
714        if array.is_null($index) {
715            Ok(Variant::Null)
716        } else {
717            let cast_value = $cast_fn(array.value($index));
718            Ok(Variant::from(cast_value))
719        }
720    }};
721}
722pub(crate) use non_generic_conversion_single_value;
723
724/// Convert the value at a specific index in the given array into a `Variant`,
725/// using `method` requiring a generic type to downcast the generic array
726/// to a specific array type and `cast_fn` to transform the element.
727macro_rules! generic_conversion_single_value {
728    ($t:ty, $method:ident, $cast_fn:expr, $input:expr, $index:expr) => {{
729        $crate::type_conversion::non_generic_conversion_single_value!(
730            $input.$method::<$t>(),
731            $cast_fn,
732            $index
733        )
734    }};
735}
736pub(crate) use generic_conversion_single_value;
737
738macro_rules! generic_conversion_single_value_with_result {
739    ($t:ty, $method:ident, $cast_fn:expr, $input:expr, $index:expr) => {{
740        let arr = $input.$method::<$t>();
741        let v = arr.value($index);
742        match ($cast_fn)(v) {
743            Ok(var) => Ok(Variant::from(var)),
744            Err(e) => Err(ArrowError::CastError(format!(
745                "Cast failed at index {idx} (array type: {ty}): {e}",
746                idx = $index,
747                ty = <$t as ::arrow::datatypes::ArrowPrimitiveType>::DATA_TYPE
748            ))),
749        }
750    }};
751}
752
753pub(crate) use generic_conversion_single_value_with_result;
754
755/// Convert the value at a specific index in the given array into a `Variant`.
756macro_rules! primitive_conversion_single_value {
757    ($t:ty, $input:expr, $index:expr) => {{
758        $crate::type_conversion::generic_conversion_single_value!(
759            $t,
760            as_primitive,
761            |v| v,
762            $input,
763            $index
764        )
765    }};
766}
767pub(crate) use primitive_conversion_single_value;