Skip to main content

parquet_variant_compute/
type_conversion.rs

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