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