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::{
22    CastOptions, DecimalCast, cast_num_to_bool, cast_single_string_to_boolean_default, num_cast,
23    parse_string_to_decimal_native, rescale_decimal, single_bool_to_numeric,
24    single_decimal_to_float_lossy, single_float_to_decimal,
25};
26use arrow::datatypes::{
27    self, ArrowPrimitiveType, ArrowTimestampType, Decimal32Type, Decimal64Type, Decimal128Type,
28    Decimal256Type, DecimalType,
29};
30use arrow::error::{ArrowError, Result};
31use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Timelike, Utc};
32use half::f16;
33use num_traits::NumCast;
34use parquet_variant::{Variant, VariantDecimal4, VariantDecimal8, VariantDecimal16};
35
36/// Extension trait for Arrow primitive types that can extract their native value from a Variant
37pub(crate) trait PrimitiveFromVariant: ArrowPrimitiveType {
38    fn from_variant(variant: &Variant<'_, '_>, shred: bool) -> Option<Self::Native>;
39}
40
41/// Extension trait for Arrow timestamp types that can extract their native value from a Variant
42/// We can't use [`PrimitiveFromVariant`] directly because we need _two_ implementations for each
43/// timestamp type -- the `NTZ` param here.
44pub(crate) trait TimestampFromVariant<const NTZ: bool>: ArrowTimestampType {
45    fn from_variant(variant: &Variant<'_, '_>, shred: bool) -> Option<Self::Native>;
46}
47
48/// Cast a single `Variant` value with safe/strict semantics.
49///
50/// Returns `Ok(Some(_))` on successful conversion.
51/// Returns `Ok(None)` when conversion fails in safe mode or the source value is `Variant::Null`.
52/// Returns `Err(_)` when conversion fails in strict mode.
53pub(crate) fn variant_cast_with_options<'a, 'm, 'v, T>(
54    variant: &'a Variant<'m, 'v>,
55    cast_options: &CastOptions<'_>,
56    cast: impl FnOnce(&'a Variant<'m, 'v>) -> Option<T>,
57) -> Result<Option<T>> {
58    if let Some(value) = cast(variant) {
59        Ok(Some(value))
60    } else if matches!(variant, Variant::Null) || cast_options.safe {
61        Ok(None)
62    } else {
63        Err(ArrowError::CastError(format!(
64            "Failed to cast variant value {variant:?}"
65        )))
66    }
67}
68
69/// Macro to generate PrimitiveFromVariant implementations for Arrow primitive types
70macro_rules! impl_primitive_from_variant {
71    ($arrow_type:ty, $shred_fun:expr, $get_method:ident $(, $cast_fn:expr)?) => {
72        impl PrimitiveFromVariant for $arrow_type {
73            fn from_variant(variant: &Variant<'_, '_>, shred: bool) -> Option<Self::Native> {
74                let value = match shred {
75                    true => $shred_fun(variant),
76                    false => $get_method(variant),
77                };
78                $( let value = value.and_then($cast_fn); )?
79                value
80            }
81        }
82    };
83}
84
85macro_rules! impl_timestamp_from_variant {
86    ($timestamp_type:ty, $shred_fun:expr, $variant_method:expr, ntz=$ntz:ident, $cast_fn:expr $(,)?) => {
87        impl TimestampFromVariant<{ $ntz }> for $timestamp_type {
88            fn from_variant(variant: &Variant<'_, '_>, shred: bool) -> Option<Self::Native> {
89                let value = match shred {
90                    true => ($shred_fun)(variant),
91                    false => $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/// `single_float_to_decimal` for floats, and `parse_string_to_decimal_native` 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    let mul = 10_f64.powi(scale as i32);
384
385    match variant {
386        Variant::Int8(i) => rescale_decimal::<Decimal32Type, O>(
387            *i as i32,
388            VariantDecimal4::MAX_PRECISION,
389            0,
390            precision,
391            scale,
392        ),
393        Variant::Int16(i) => rescale_decimal::<Decimal32Type, O>(
394            *i as i32,
395            VariantDecimal4::MAX_PRECISION,
396            0,
397            precision,
398            scale,
399        ),
400        Variant::Int32(i) => rescale_decimal::<Decimal32Type, O>(
401            *i,
402            VariantDecimal4::MAX_PRECISION,
403            0,
404            precision,
405            scale,
406        ),
407        Variant::Int64(i) => rescale_decimal::<Decimal64Type, O>(
408            *i,
409            VariantDecimal8::MAX_PRECISION,
410            0,
411            precision,
412            scale,
413        ),
414        Variant::Float(f) => single_float_to_decimal::<O>(<f64 as From<f32>>::from(*f), mul),
415        Variant::Double(f) => single_float_to_decimal::<O>(*f, mul),
416        // arrow-cast only support cast string to decimal with scale >=0 for now
417        // Please see `cast_string_to_decimal` in arrow-cast/src/cast/decimal.rs for more detail
418        Variant::String(v) if scale >= 0 => parse_string_to_decimal_native::<O>(v, scale as _).ok(),
419        Variant::ShortString(v) if scale >= 0 => {
420            parse_string_to_decimal_native::<O>(v, scale as _).ok()
421        }
422        Variant::Decimal4(d) => rescale_decimal::<Decimal32Type, O>(
423            d.integer(),
424            VariantDecimal4::MAX_PRECISION,
425            d.scale() as i8,
426            precision,
427            scale,
428        ),
429        Variant::Decimal8(d) => rescale_decimal::<Decimal64Type, O>(
430            d.integer(),
431            VariantDecimal8::MAX_PRECISION,
432            d.scale() as i8,
433            precision,
434            scale,
435        ),
436        Variant::Decimal16(d) => rescale_decimal::<Decimal128Type, O>(
437            d.integer(),
438            VariantDecimal16::MAX_PRECISION,
439            d.scale() as i8,
440            precision,
441            scale,
442        ),
443        _ => None,
444    }
445}
446
447/// Returns the unscaled integer representation for Arrow decimal type `O` from a `Variant`.
448///
449/// Unlike `variant_to_unscaled_decimal`, this function only accepts integer and decimal
450/// variants. Decimal values may be rescaled only when the conversion is exact, as verified
451/// by converting the result back to the original scale.
452pub(crate) fn shred_variant_to_unscaled_decimal<O>(
453    variant: &Variant<'_, '_>,
454    precision: u8,
455    scale: i8,
456) -> Option<O::Native>
457where
458    O: ShredDecimalVariant,
459    O::Native: DecimalCast,
460{
461    match variant {
462        Variant::Int8(_)
463        | Variant::Int16(_)
464        | Variant::Int32(_)
465        | Variant::Int64(_)
466        | Variant::Decimal4(_)
467        | Variant::Decimal8(_)
468        | Variant::Decimal16(_) => O::shred_variant(variant, precision, scale),
469        _ => None,
470    }
471}
472pub(crate) trait ShredDecimalVariant: DecimalType {
473    fn shred_variant(value: &Variant<'_, '_>, precision: u8, scale: i8) -> Option<Self::Native>;
474}
475
476fn convert_to_unscaled_decimal<I, O>(
477    input: I::Native,
478    input_precision: u8,
479    input_scale: i8,
480    target_precision: u8,
481    target_scale: i8,
482) -> Option<O::Native>
483where
484    I: DecimalType,
485    O: DecimalType,
486    I::Native: DecimalCast,
487    O::Native: DecimalCast,
488{
489    let converted = rescale_decimal::<I, O>(
490        input,
491        input_precision,
492        input_scale,
493        target_precision,
494        target_scale,
495    )?;
496
497    let converted_back = rescale_decimal::<O, I>(
498        converted,
499        target_precision,
500        target_scale,
501        input_precision,
502        input_scale,
503    )?;
504    if converted_back == input {
505        return Some(converted);
506    }
507
508    None
509}
510
511impl ShredDecimalVariant for Decimal32Type {
512    fn shred_variant(value: &Variant<'_, '_>, precision: u8, scale: i8) -> Option<Self::Native> {
513        match *value {
514            Variant::Int8(i) => convert_to_unscaled_decimal::<Decimal32Type, Decimal32Type>(
515                i as i32,
516                VariantDecimal4::MAX_PRECISION,
517                0,
518                precision,
519                scale,
520            ),
521            Variant::Int16(i) => convert_to_unscaled_decimal::<Decimal32Type, Decimal32Type>(
522                i as i32,
523                VariantDecimal4::MAX_PRECISION,
524                0,
525                precision,
526                scale,
527            ),
528            Variant::Int32(i) => convert_to_unscaled_decimal::<Decimal32Type, Decimal32Type>(
529                i,
530                VariantDecimal4::MAX_PRECISION,
531                0,
532                precision,
533                scale,
534            ),
535            Variant::Int64(i) => {
536                let i32_value = <i64 as TryInto<i32>>::try_into(i).ok()?;
537                convert_to_unscaled_decimal::<Decimal32Type, Decimal32Type>(
538                    i32_value,
539                    VariantDecimal4::MAX_PRECISION,
540                    0,
541                    precision,
542                    scale,
543                )
544            }
545            Variant::Decimal4(d) => convert_to_unscaled_decimal::<Decimal32Type, Decimal32Type>(
546                d.integer(),
547                VariantDecimal4::MAX_PRECISION,
548                d.scale() as i8,
549                precision,
550                scale,
551            ),
552            Variant::Decimal8(d) => convert_to_unscaled_decimal::<Decimal64Type, Decimal32Type>(
553                d.integer(),
554                VariantDecimal8::MAX_PRECISION,
555                d.scale() as i8,
556                precision,
557                scale,
558            ),
559            Variant::Decimal16(d) => convert_to_unscaled_decimal::<Decimal128Type, Decimal32Type>(
560                d.integer(),
561                VariantDecimal16::MAX_PRECISION,
562                d.scale() as i8,
563                precision,
564                scale,
565            ),
566            _ => None,
567        }
568    }
569}
570
571impl ShredDecimalVariant for Decimal64Type {
572    fn shred_variant(value: &Variant<'_, '_>, precision: u8, scale: i8) -> Option<Self::Native> {
573        match *value {
574            Variant::Int8(i) => convert_to_unscaled_decimal::<Decimal64Type, Decimal64Type>(
575                i as i64,
576                VariantDecimal8::MAX_PRECISION,
577                0,
578                precision,
579                scale,
580            ),
581            Variant::Int16(i) => convert_to_unscaled_decimal::<Decimal64Type, Decimal64Type>(
582                i as i64,
583                VariantDecimal8::MAX_PRECISION,
584                0,
585                precision,
586                scale,
587            ),
588            Variant::Int32(i) => convert_to_unscaled_decimal::<Decimal64Type, Decimal64Type>(
589                i as i64,
590                VariantDecimal8::MAX_PRECISION,
591                0,
592                precision,
593                scale,
594            ),
595            Variant::Int64(i) => convert_to_unscaled_decimal::<Decimal64Type, Decimal64Type>(
596                i,
597                VariantDecimal8::MAX_PRECISION,
598                0,
599                precision,
600                scale,
601            ),
602            Variant::Decimal4(d) => convert_to_unscaled_decimal::<Decimal32Type, Decimal64Type>(
603                d.integer(),
604                VariantDecimal4::MAX_PRECISION,
605                d.scale() as i8,
606                precision,
607                scale,
608            ),
609            Variant::Decimal8(d) => convert_to_unscaled_decimal::<Decimal64Type, Decimal64Type>(
610                d.integer(),
611                VariantDecimal8::MAX_PRECISION,
612                d.scale() as i8,
613                precision,
614                scale,
615            ),
616            Variant::Decimal16(d) => convert_to_unscaled_decimal::<Decimal128Type, Decimal64Type>(
617                d.integer(),
618                VariantDecimal16::MAX_PRECISION,
619                d.scale() as i8,
620                precision,
621                scale,
622            ),
623            _ => None,
624        }
625    }
626}
627
628impl ShredDecimalVariant for Decimal128Type {
629    fn shred_variant(value: &Variant<'_, '_>, precision: u8, scale: i8) -> Option<Self::Native> {
630        match *value {
631            Variant::Int8(i) => convert_to_unscaled_decimal::<Decimal128Type, Decimal128Type>(
632                i as i128,
633                VariantDecimal4::MAX_PRECISION,
634                0,
635                precision,
636                scale,
637            ),
638            Variant::Int16(i) => convert_to_unscaled_decimal::<Decimal128Type, Decimal128Type>(
639                i as i128,
640                VariantDecimal4::MAX_PRECISION,
641                0,
642                precision,
643                scale,
644            ),
645            Variant::Int32(i) => convert_to_unscaled_decimal::<Decimal128Type, Decimal128Type>(
646                i as i128,
647                VariantDecimal4::MAX_PRECISION,
648                0,
649                precision,
650                scale,
651            ),
652            Variant::Int64(i) => convert_to_unscaled_decimal::<Decimal128Type, Decimal128Type>(
653                i as i128,
654                VariantDecimal4::MAX_PRECISION,
655                0,
656                precision,
657                scale,
658            ),
659            Variant::Decimal4(d) => convert_to_unscaled_decimal::<Decimal32Type, Decimal128Type>(
660                d.integer(),
661                VariantDecimal4::MAX_PRECISION,
662                d.scale() as i8,
663                precision,
664                scale,
665            ),
666            Variant::Decimal8(d) => convert_to_unscaled_decimal::<Decimal64Type, Decimal128Type>(
667                d.integer(),
668                VariantDecimal8::MAX_PRECISION,
669                d.scale() as i8,
670                precision,
671                scale,
672            ),
673            Variant::Decimal16(d) => convert_to_unscaled_decimal::<Decimal128Type, Decimal128Type>(
674                d.integer(),
675                VariantDecimal16::MAX_PRECISION,
676                d.scale() as i8,
677                precision,
678                scale,
679            ),
680            _ => None,
681        }
682    }
683}
684
685impl ShredDecimalVariant for Decimal256Type {
686    fn shred_variant(_value: &Variant<'_, '_>, _precision: u8, _scale: i8) -> Option<Self::Native> {
687        None // always return none because we'll never shred to decimal256
688    }
689}
690
691pub(crate) fn variant_to_boolean(variant: &Variant<'_, '_>, shred: bool) -> Option<bool> {
692    if shred {
693        return variant.as_boolean();
694    }
695
696    match variant {
697        Variant::BooleanTrue => Some(true),
698        Variant::BooleanFalse => Some(false),
699        Variant::Int8(i) => Some(cast_num_to_bool(*i)),
700        Variant::Int16(i) => Some(cast_num_to_bool(*i)),
701        Variant::Int32(i) => Some(cast_num_to_bool(*i)),
702        Variant::Int64(i) => Some(cast_num_to_bool(*i)),
703        Variant::Float(f) => Some(cast_num_to_bool(*f)),
704        Variant::Double(d) => Some(cast_num_to_bool(*d)),
705        Variant::ShortString(s) => cast_single_string_to_boolean_default(s.as_str()),
706        Variant::String(s) => cast_single_string_to_boolean_default(s),
707        _ => None,
708    }
709}
710
711/// Convert the value at a specific index in the given array into a `Variant`.
712macro_rules! non_generic_conversion_single_value {
713    ($array:expr, $cast_fn:expr, $index:expr) => {{
714        let array = $array;
715        if array.is_null($index) {
716            Ok(Variant::Null)
717        } else {
718            let cast_value = $cast_fn(array.value($index));
719            Ok(Variant::from(cast_value))
720        }
721    }};
722}
723pub(crate) use non_generic_conversion_single_value;
724
725/// Convert the value at a specific index in the given array into a `Variant`,
726/// using `method` requiring a generic type to downcast the generic array
727/// to a specific array type and `cast_fn` to transform the element.
728macro_rules! generic_conversion_single_value {
729    ($t:ty, $method:ident, $cast_fn:expr, $input:expr, $index:expr) => {{
730        $crate::type_conversion::non_generic_conversion_single_value!(
731            $input.$method::<$t>(),
732            $cast_fn,
733            $index
734        )
735    }};
736}
737pub(crate) use generic_conversion_single_value;
738
739macro_rules! generic_conversion_single_value_with_result {
740    ($t:ty, $method:ident, $cast_fn:expr, $input:expr, $index:expr) => {{
741        let arr = $input.$method::<$t>();
742        let v = arr.value($index);
743        match ($cast_fn)(v) {
744            Ok(var) => Ok(Variant::from(var)),
745            Err(e) => Err(ArrowError::CastError(format!(
746                "Cast failed at index {idx} (array type: {ty}): {e}",
747                idx = $index,
748                ty = <$t as ::arrow::datatypes::ArrowPrimitiveType>::DATA_TYPE
749            ))),
750        }
751    }};
752}
753
754pub(crate) use generic_conversion_single_value_with_result;
755
756/// Convert the value at a specific index in the given array into a `Variant`.
757macro_rules! primitive_conversion_single_value {
758    ($t:ty, $input:expr, $index:expr) => {{
759        $crate::type_conversion::generic_conversion_single_value!(
760            $t,
761            as_primitive,
762            |v| v,
763            $input,
764            $index
765        )
766    }};
767}
768pub(crate) use primitive_conversion_single_value;