Skip to main content

vortex_array/arrays/primitive/compute/
cast.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use num_traits::AsPrimitive;
5use num_traits::CheckedMul;
6use num_traits::NumCast;
7use vortex_buffer::Buffer;
8use vortex_buffer::BufferMut;
9use vortex_compute::lane_kernels::IndexedSinkExt;
10use vortex_compute::lane_kernels::IndexedSourceExt;
11use vortex_compute::lane_kernels::ReinterpretSink;
12use vortex_error::VortexError;
13use vortex_error::VortexResult;
14use vortex_error::vortex_bail;
15use vortex_error::vortex_err;
16use vortex_mask::Mask;
17
18use crate::ArrayRef;
19use crate::ExecutionCtx;
20use crate::IntoArray;
21use crate::aggregate_fn;
22use crate::array::ArrayView;
23use crate::arrays::ConstantArray;
24use crate::arrays::DecimalArray;
25use crate::arrays::Primitive;
26use crate::arrays::PrimitiveArray;
27use crate::arrays::primitive::PrimitiveArrayExt;
28use crate::dtype::BigCast;
29use crate::dtype::DType;
30use crate::dtype::DecimalDType;
31use crate::dtype::DecimalType;
32use crate::dtype::IntegerPType;
33use crate::dtype::NativeDecimalType;
34use crate::dtype::NativePType;
35use crate::dtype::Nullability;
36use crate::dtype::PType;
37use crate::dtype::ToI256;
38use crate::dtype::i256;
39use crate::expr::stats::Stat;
40use crate::expr::stats::StatsProvider;
41use crate::match_each_decimal_value_type;
42use crate::match_each_integer_ptype;
43use crate::match_each_native_ptype;
44use crate::match_each_signed_integer_ptype;
45use crate::scalar::DecimalValue;
46use crate::scalar::Scalar;
47use crate::scalar_fn::fns::cast::CastKernel;
48use crate::scalar_fn::fns::cast::CastReduce;
49use crate::validity::Validity;
50
51impl CastReduce for Primitive {
52    fn cast(array: ArrayView<'_, Primitive>, dtype: &DType) -> VortexResult<Option<ArrayRef>> {
53        // Only the same ptype is reducible without execution; type changes need the kernel
54        // to verify values fit in the target range.
55        let DType::Primitive(new_ptype, new_nullability) = dtype else {
56            return Ok(None);
57        };
58        if *new_ptype != array.ptype() {
59            return Ok(None);
60        }
61
62        let Some(new_validity) = array
63            .validity()?
64            .trivially_cast_nullability(*new_nullability, array.len())?
65        else {
66            return Ok(None);
67        };
68
69        // SAFETY: validity and data buffer still have same length.
70        Ok(Some(unsafe {
71            PrimitiveArray::new_unchecked_from_handle(
72                array.buffer_handle().clone(),
73                array.ptype(),
74                new_validity,
75            )
76            .into_array()
77        }))
78    }
79}
80
81impl CastKernel for Primitive {
82    fn cast(
83        array: ArrayView<'_, Primitive>,
84        dtype: &DType,
85        ctx: &mut ExecutionCtx,
86    ) -> VortexResult<Option<ArrayRef>> {
87        if let DType::Decimal(decimal_dtype, nullability) = dtype {
88            return cast_to_decimal(array, *decimal_dtype, *nullability, ctx).map(Some);
89        }
90        let DType::Primitive(new_ptype, new_nullability) = dtype else {
91            return Ok(None);
92        };
93        let (new_ptype, new_nullability) = (*new_ptype, *new_nullability);
94        let src_ptype = array.ptype();
95
96        let new_validity = array
97            .validity()?
98            .cast_nullability(new_nullability, array.len(), ctx)?;
99
100        // Same bit representation: either the same ptype (only the nullability changed) or two
101        // same-width integers (identical layout under 2's complement). The only non-trivial case
102        // is the sign change between same-width ints, which still needs a value-range check.
103        let same_rep = src_ptype == new_ptype
104            || (src_ptype.is_int()
105                && new_ptype.is_int()
106                && src_ptype.byte_width() == new_ptype.byte_width());
107        if same_rep {
108            if !values_fit_in(array, new_ptype, ctx, true) {
109                vortex_bail!(
110                    Compute: "Cannot cast {} to {} — values exceed target range",
111                    src_ptype, new_ptype,
112                );
113            }
114            return Ok(Some(reinterpret(array, new_ptype, new_validity)));
115        }
116
117        // Different bit rep: cast each element. `cast_values` picks a pure or checked loop based
118        // on whether the conversion is statically infallible.
119        Ok(Some(match_each_native_ptype!(new_ptype, |T| {
120            match_each_native_ptype!(src_ptype, |F| {
121                cast_values::<F, T>(array, new_validity, ctx)?
122            })
123        })))
124    }
125}
126
127fn cast_to_decimal(
128    array: ArrayView<'_, Primitive>,
129    decimal_dtype: DecimalDType,
130    nullability: Nullability,
131    ctx: &mut ExecutionCtx,
132) -> VortexResult<ArrayRef> {
133    if !array.ptype().is_int() {
134        vortex_bail!(
135            Compute: "Cannot cast floating primitive {} to decimal {}",
136            array.ptype(), decimal_dtype
137        );
138    }
139
140    let source_validity = array.validity()?;
141    let validity = source_validity
142        .clone()
143        .cast_nullability(nullability, array.len(), ctx)?;
144    let values_type = DecimalType::smallest_decimal_value_type(&decimal_dtype);
145
146    if decimal_dtype.scale() == 0
147        && signed_primitive_decimal_type(array.ptype()) == Some(values_type)
148    {
149        return match_each_signed_integer_ptype!(array.ptype(), |S| {
150            cast_unscaled_same_width_signed_integer_to_decimal::<S>(
151                array,
152                decimal_dtype,
153                validity,
154                &source_validity,
155                ctx,
156            )
157        });
158    }
159
160    let valid_values = source_validity.execute_mask(array.len(), ctx)?;
161    if !array.is_empty() && matches!(valid_values, Mask::AllFalse(_)) {
162        return Ok(ConstantArray::new(
163            Scalar::null(DType::Decimal(decimal_dtype, nullability)),
164            array.len(),
165        )
166        .into_array());
167    }
168    match_each_integer_ptype!(array.ptype(), |S| {
169        match_each_decimal_value_type!(values_type, |T| {
170            cast_integer_values_to_decimal::<S, T>(array, decimal_dtype, validity, &valid_values)
171        })
172    })
173}
174
175fn cast_unscaled_same_width_signed_integer_to_decimal<S>(
176    array: ArrayView<'_, Primitive>,
177    decimal_dtype: DecimalDType,
178    validity: Validity,
179    source_validity: &Validity,
180    ctx: &mut ExecutionCtx,
181) -> VortexResult<ArrayRef>
182where
183    S: IntegerPType + NativeDecimalType + ToI256,
184{
185    let values = array.as_slice::<S>();
186    let target_dtype = DType::Decimal(decimal_dtype, Nullability::NonNullable);
187    if !cached_values_fit_in(array, &target_dtype).unwrap_or(false) {
188        let valid_values = source_validity.execute_mask(array.len(), ctx)?;
189        validate_unscaled_signed_integer_values_to_decimal(values, decimal_dtype, &valid_values)
190            .map_err(|idx| primitive_to_decimal_cast_error(values[idx], decimal_dtype))?;
191    }
192
193    // SAFETY: `S::DECIMAL_TYPE` has the same physical representation as the source ptype, and
194    // either exact min/max statistics or the validation above prove every valid value fits.
195    Ok(unsafe {
196        DecimalArray::new_unchecked_handle(
197            array.buffer_handle().clone(),
198            S::DECIMAL_TYPE,
199            decimal_dtype,
200            validity,
201        )
202        .into_array()
203    })
204}
205
206fn validate_unscaled_signed_integer_values_to_decimal<S>(
207    values: &[S],
208    decimal_dtype: DecimalDType,
209    valid_values: &Mask,
210) -> Result<(), usize>
211where
212    S: NativeDecimalType,
213{
214    let fits = |value| decimal_value_fits_precision(value, decimal_dtype);
215    match valid_values {
216        Mask::AllTrue(_) => values
217            .iter()
218            .position(|&value| !fits(value))
219            .map_or(Ok(()), Err),
220        Mask::AllFalse(_) => Ok(()),
221        Mask::Values(mask) => {
222            let mut first_failure = None;
223            mask.bit_buffer().for_each_set_index(|idx| {
224                if first_failure.is_none() && !fits(values[idx]) {
225                    first_failure = Some(idx);
226                }
227            });
228            first_failure.map_or(Ok(()), Err)
229        }
230    }
231}
232
233fn cast_integer_values_to_decimal<S, T>(
234    array: ArrayView<'_, Primitive>,
235    decimal_dtype: DecimalDType,
236    validity: Validity,
237    valid_values: &Mask,
238) -> VortexResult<ArrayRef>
239where
240    S: IntegerPType + ToI256,
241    T: NativeDecimalType + CheckedMul,
242{
243    let scale = decimal_dtype.scale();
244    let buffer = if scale == 0 {
245        cast_unscaled_integer_values_to_decimal::<S, T>(array, decimal_dtype, valid_values)?
246    } else if scale > 0 {
247        cast_scaled_up_integer_values_to_decimal::<S, T>(array, decimal_dtype, valid_values)?
248    } else {
249        cast_scaled_down_integer_values_to_decimal::<S, T>(array, decimal_dtype, valid_values)?
250    };
251
252    Ok(DecimalArray::new(buffer, decimal_dtype, validity).into_array())
253}
254
255fn cast_unscaled_integer_values_to_decimal<S, T>(
256    array: ArrayView<'_, Primitive>,
257    decimal_dtype: DecimalDType,
258    valid_values: &Mask,
259) -> VortexResult<Buffer<T>>
260where
261    S: IntegerPType + ToI256,
262    T: NativeDecimalType,
263{
264    let values = array.as_slice::<S>();
265    cast_integer_values_to_decimal_buffer(values, decimal_dtype, valid_values, |value| {
266        let value = <T as BigCast>::from(value)?;
267        decimal_value_fits_precision(value, decimal_dtype).then_some(value)
268    })
269}
270
271fn cast_scaled_up_integer_values_to_decimal<S, T>(
272    array: ArrayView<'_, Primitive>,
273    decimal_dtype: DecimalDType,
274    valid_values: &Mask,
275) -> VortexResult<Buffer<T>>
276where
277    S: IntegerPType + ToI256,
278    T: NativeDecimalType + CheckedMul,
279{
280    let values = array.as_slice::<S>();
281    let scale_factor = decimal_scale_factor::<T>(decimal_dtype.scale())?;
282    cast_integer_values_to_decimal_buffer(values, decimal_dtype, valid_values, |value| {
283        let value = <T as BigCast>::from(value)?;
284        let value = value.checked_mul(&scale_factor)?;
285        decimal_value_fits_precision(value, decimal_dtype).then_some(value)
286    })
287}
288
289fn cast_scaled_down_integer_values_to_decimal<S, T>(
290    array: ArrayView<'_, Primitive>,
291    decimal_dtype: DecimalDType,
292    valid_values: &Mask,
293) -> VortexResult<Buffer<T>>
294where
295    S: IntegerPType + ToI256,
296    T: NativeDecimalType,
297{
298    let values = array.as_slice::<S>();
299    if decimal_dtype.scale().unsigned_abs() >= primitive_max_decimal_digits(array.ptype()) {
300        // The scale factor exceeds every source value, so only zero can be exactly rescaled.
301        return cast_integer_values_to_decimal_buffer(
302            values,
303            decimal_dtype,
304            valid_values,
305            |value| (value == S::default()).then_some(T::default()),
306        );
307    }
308
309    // Scaling down can shrink a value into a narrower target type, so first select the smallest
310    // signed carrier that can represent both the source and target.
311    let carrier_type = primitive_decimal_carrier_type(array.ptype()).max(T::DECIMAL_TYPE);
312    match_each_decimal_value_type!(carrier_type, |W| {
313        let scale_factor = decimal_scale_factor::<W>(decimal_dtype.scale())?;
314        cast_integer_values_to_decimal_buffer(values, decimal_dtype, valid_values, |value| {
315            let value = <W as BigCast>::from(value)?;
316            let quotient = value / scale_factor;
317            let value = (quotient * scale_factor == value).then_some(quotient)?;
318            let value = <T as BigCast>::from(value)?;
319            decimal_value_fits_precision(value, decimal_dtype).then_some(value)
320        })
321    })
322}
323
324fn cast_integer_values_to_decimal_buffer<S, T>(
325    values: &[S],
326    decimal_dtype: DecimalDType,
327    valid_values: &Mask,
328    cast: impl Fn(S) -> Option<T>,
329) -> VortexResult<Buffer<T>>
330where
331    S: IntegerPType + ToI256,
332    T: NativeDecimalType,
333{
334    cast_primitive_to_decimal_buffer(values, valid_values, cast)
335        .map_err(|idx| primitive_to_decimal_cast_error(values[idx], decimal_dtype))
336}
337
338/// Decimal physical type with the same representation, when `ptype` is signed.
339fn signed_primitive_decimal_type(ptype: PType) -> Option<DecimalType> {
340    match ptype {
341        PType::I8 => Some(DecimalType::I8),
342        PType::I16 => Some(DecimalType::I16),
343        PType::I32 => Some(DecimalType::I32),
344        PType::I64 => Some(DecimalType::I64),
345        PType::U8 | PType::U16 | PType::U32 | PType::U64 => None,
346        PType::F16 | PType::F32 | PType::F64 => {
347            unreachable!("floating primitives are rejected before selecting a decimal type")
348        }
349    }
350}
351
352/// The smallest signed decimal physical type that represents every `ptype` value.
353fn primitive_decimal_carrier_type(ptype: PType) -> DecimalType {
354    match ptype {
355        PType::I8 => DecimalType::I8,
356        PType::U8 | PType::I16 => DecimalType::I16,
357        PType::U16 | PType::I32 => DecimalType::I32,
358        PType::U32 | PType::I64 => DecimalType::I64,
359        PType::U64 => DecimalType::I128,
360        PType::F16 | PType::F32 | PType::F64 => {
361            unreachable!("floating primitives are rejected before selecting a decimal carrier")
362        }
363    }
364}
365
366/// Maximum decimal digits in `ptype`; scales at least this large can exactly rescale only zero.
367fn primitive_max_decimal_digits(ptype: PType) -> u8 {
368    match ptype {
369        PType::U8 | PType::I8 => 3,
370        PType::U16 | PType::I16 => 5,
371        PType::U32 | PType::I32 => 10,
372        PType::U64 => 20,
373        PType::I64 => 19,
374        PType::F16 | PType::F32 | PType::F64 => {
375            unreachable!("floating primitives are rejected before inspecting decimal digits")
376        }
377    }
378}
379
380fn decimal_scale_factor<T>(scale: i8) -> VortexResult<T>
381where
382    T: NativeDecimalType + CheckedMul,
383{
384    let exponent = if scale > 0 {
385        scale as u32
386    } else {
387        (-(scale as i16)) as u32
388    };
389    let ten = <T as BigCast>::from(10i8).ok_or_else(|| scale_factor_error(scale))?;
390    let mut factor = <T as BigCast>::from(1i8).ok_or_else(|| scale_factor_error(scale))?;
391    for _ in 0..exponent {
392        factor = factor
393            .checked_mul(&ten)
394            .ok_or_else(|| scale_factor_error(scale))?;
395    }
396    Ok(factor)
397}
398
399#[cold]
400#[inline(never)]
401fn scale_factor_error(scale: i8) -> VortexError {
402    vortex_err!(Compute: "Cannot create decimal scale factor for scale {scale}")
403}
404
405fn decimal_value_fits_precision<T: NativeDecimalType>(
406    value: T,
407    decimal_dtype: DecimalDType,
408) -> bool {
409    let precision = decimal_dtype.precision() as usize;
410    value >= T::MIN_BY_PRECISION[precision] && value <= T::MAX_BY_PRECISION[precision]
411}
412
413fn cast_primitive_to_decimal_buffer<S, T>(
414    values: &[S],
415    valid_values: &Mask,
416    cast: impl Fn(S) -> Option<T>,
417) -> Result<Buffer<T>, usize>
418where
419    S: NativePType,
420    T: NativeDecimalType,
421{
422    if matches!(valid_values, Mask::AllFalse(_)) {
423        return Ok(BufferMut::<T>::zeroed(values.len()).freeze());
424    }
425
426    let mut buffer = BufferMut::<T>::with_capacity(values.len());
427    match valid_values {
428        Mask::AllTrue(_) => {
429            values.try_map_into(&mut buffer.spare_capacity_mut()[..values.len()], &cast)?;
430        }
431        Mask::Values(mask) => {
432            values.try_map_masked_into(
433                mask.bit_buffer(),
434                &mut buffer.spare_capacity_mut()[..values.len()],
435                &cast,
436            )?;
437        }
438        Mask::AllFalse(_) => unreachable!("all-null values are handled before allocating"),
439    }
440    // SAFETY: the selected map kernel initialized every lane before returning Ok.
441    unsafe { buffer.set_len(values.len()) };
442    Ok(buffer.freeze())
443}
444
445#[cold]
446#[inline(never)]
447fn primitive_to_decimal_cast_error<S>(value: S, decimal_dtype: DecimalDType) -> VortexError
448where
449    S: IntegerPType + ToI256,
450{
451    let Some(value) = <i256 as BigCast>::from(value) else {
452        return vortex_err!(
453            Compute: "primitive value cannot be represented while casting to {}",
454            decimal_dtype
455        );
456    };
457
458    match DecimalValue::rescale_i256(value, 0, decimal_dtype.scale())
459        .and_then(|value| DecimalValue::try_from_i256(value, decimal_dtype))
460    {
461        Err(error) => error,
462        Ok(_) => {
463            debug_assert!(
464                false,
465                "primitive-to-decimal fast path rejected a value that the scalar cast accepts"
466            );
467            vortex_err!(
468                Compute: "primitive value cannot be represented while casting to {}",
469                decimal_dtype
470            )
471        }
472    }
473}
474
475/// Cast Primitive values from `F` to `T`.
476fn cast_values<F, T>(
477    array: ArrayView<'_, Primitive>,
478    new_validity: Validity,
479    ctx: &mut ExecutionCtx,
480) -> VortexResult<ArrayRef>
481where
482    F: NativePType + AsPrimitive<T>,
483    T: NativePType,
484{
485    let overflow = || {
486        vortex_err!(
487            Compute: "Cannot cast {} to {} — value exceeds target range",
488            F::PTYPE, T::PTYPE,
489        )
490    };
491
492    // Returns `true` if every value of `from` is representable in `to` without loss.
493    fn casts_losslessly_to(from: PType, to: PType) -> bool {
494        if from == to {
495            return true;
496        }
497        if (from.is_unsigned_int() && to.is_unsigned_int())
498            || (from.is_signed_int() && to.is_signed_int())
499            || (from.is_float() && to.is_float())
500        {
501            return from.byte_width() <= to.byte_width();
502        }
503        if from.is_unsigned_int() && to.is_signed_int() {
504            return from.byte_width() < to.byte_width();
505        }
506        if from.is_int() && to.is_float() {
507            let minimum_float_width = match from.byte_width() {
508                1 => 2,
509                2 => 4,
510                4 => 8,
511                _ => return false,
512            };
513            return to.byte_width() >= minimum_float_width;
514        }
515        false
516    }
517
518    // Skip the fallible kernel when type widening or (cached) min/max prove every value fits.
519    let target_dtype = DType::Primitive(T::PTYPE, Nullability::NonNullable);
520    let infallible = casts_losslessly_to(F::PTYPE, T::PTYPE)
521        || cached_values_fit_in(array, &target_dtype).unwrap_or(false);
522
523    let len = array.len();
524
525    // If F and T have the same byte width, try to take unique ownership of the buffer.
526    let same_bit_width = F::PTYPE.byte_width() == T::PTYPE.byte_width();
527    let owned: Option<BufferMut<F>> = same_bit_width
528        .then(|| array.into_owned().try_into_buffer_mut::<F>().ok())
529        .flatten();
530    let values: &[F] = array.as_slice::<F>();
531
532    if infallible {
533        return match owned {
534            Some(mut buf) => {
535                ReinterpretSink::<F, T>::new(buf.as_mut_slice()).map_into_in_place(|v: F| v.as_());
536                // SAFETY: same size + alignment for NativePType
537                let result: BufferMut<T> = unsafe { buf.transmute::<T>() };
538                Ok(PrimitiveArray::new(result.freeze(), new_validity).into_array())
539            }
540            None => {
541                let mut buffer = BufferMut::<T>::with_capacity(len);
542                values.map_into(&mut buffer.spare_capacity_mut()[..len], |v| v.as_());
543                // SAFETY: map_into initializes every lane.
544                unsafe { buffer.set_len(len) };
545                Ok(PrimitiveArray::new(buffer.freeze(), new_validity).into_array())
546            }
547        };
548    }
549
550    let mask = array.validity()?.execute_mask(len, ctx)?;
551    if len > 0 && matches!(mask, Mask::AllFalse(_)) {
552        return Ok(ConstantArray::new(
553            Scalar::null(DType::Primitive(T::PTYPE, Nullability::Nullable)),
554            len,
555        )
556        .into_array());
557    }
558
559    let buffer: Buffer<T> = match (&mask, owned) {
560        (Mask::AllTrue(_), Some(mut buf)) => {
561            ReinterpretSink::<F, T>::new(buf.as_mut_slice())
562                .try_map_in_place(|v: F| <T as NumCast>::from(v))
563                .map_err(|_| overflow())?;
564            // SAFETY: same size + alignment for NativePType
565            let result: BufferMut<T> = unsafe { buf.transmute::<T>() };
566            result.freeze()
567        }
568        (Mask::AllTrue(_), None) => {
569            let mut buffer = BufferMut::<T>::with_capacity(len);
570            values
571                .try_map_into(&mut buffer.spare_capacity_mut()[..len], |v| {
572                    <T as NumCast>::from(v)
573                })
574                .map_err(|_| overflow())?;
575            // SAFETY: initialized every lane.
576            unsafe { buffer.set_len(len) };
577            buffer.freeze()
578        }
579        (Mask::AllFalse(_), _) => BufferMut::<T>::zeroed(len).freeze(),
580        (Mask::Values(m), Some(mut buf)) => {
581            ReinterpretSink::<F, T>::new(buf.as_mut_slice())
582                .try_map_masked_in_place(m.bit_buffer(), |v: F| <T as NumCast>::from(v))
583                .map_err(|_| overflow())?;
584            // SAFETY: same size + alignment for NativePType
585            let result: BufferMut<T> = unsafe { buf.transmute::<T>() };
586            result.freeze()
587        }
588        (Mask::Values(m), None) => {
589            let mut buffer = BufferMut::<T>::with_capacity(len);
590            values
591                .try_map_masked_into(
592                    m.bit_buffer(),
593                    &mut buffer.spare_capacity_mut()[..len],
594                    |v| <T as NumCast>::from(v),
595                )
596                .map_err(|_| overflow())?;
597            // SAFETY: initialized every lane.
598            unsafe { buffer.set_len(len) };
599            buffer.freeze()
600        }
601    };
602
603    Ok(PrimitiveArray::new(buffer, new_validity).into_array())
604}
605
606fn reinterpret(
607    array: ArrayView<'_, Primitive>,
608    new_ptype: PType,
609    new_validity: Validity,
610) -> ArrayRef {
611    // SAFETY: caller has verified the bit representation is compatible and that validity length
612    // still matches the buffer length.
613    unsafe {
614        PrimitiveArray::new_unchecked_from_handle(
615            array.buffer_handle().clone(),
616            new_ptype,
617            new_validity,
618        )
619    }
620    .into_array()
621}
622
623/// Returns `true` if all valid values in `array` are representable as `target_ptype`.
624///
625/// Cached min/max statistics are consulted first. If either bound is missing, the function either
626/// computes them with a single pass (when `compute` is `true`) or returns `false` so the caller
627/// can fall back to a slower path (when `compute` is `false`).
628fn values_fit_in(
629    array: ArrayView<'_, Primitive>,
630    target_ptype: PType,
631    ctx: &mut ExecutionCtx,
632    compute: bool,
633) -> bool {
634    let target_dtype = DType::Primitive(target_ptype, Nullability::NonNullable);
635    if let Some(fits) = cached_values_fit_in(array, &target_dtype) {
636        return fits;
637    }
638    if !compute {
639        return false;
640    }
641    aggregate_fn::fns::min_max::min_max(
642        array.array(),
643        ctx,
644        aggregate_fn::NumericalAggregateOpts::default(),
645    )
646    .ok()
647    .flatten()
648    .is_none_or(|mm| mm.min.cast(&target_dtype).is_ok() && mm.max.cast(&target_dtype).is_ok())
649}
650
651/// Cached-only check: returns `Some(fits)` if both `Min` and `Max` are present as `Exact` in the
652/// stats cache, otherwise `None`.
653fn cached_values_fit_in(array: ArrayView<'_, Primitive>, target_dtype: &DType) -> Option<bool> {
654    let stats = array.array().statistics();
655    let min = stats.get(Stat::Min).as_exact()?;
656    let max = stats.get(Stat::Max).as_exact()?;
657    Some(min.cast(target_dtype).is_ok() && max.cast(target_dtype).is_ok())
658}
659
660#[cfg(test)]
661mod test {
662    use rstest::rstest;
663    use vortex_buffer::BitBuffer;
664    use vortex_buffer::buffer;
665    use vortex_error::VortexError;
666    use vortex_error::VortexResult;
667    use vortex_mask::Mask;
668
669    use crate::ArrayRef;
670    use crate::IntoArray;
671    use crate::VortexSessionExecute;
672    use crate::array_session;
673    use crate::arrays::DecimalArray;
674    use crate::arrays::PrimitiveArray;
675    use crate::assert_arrays_eq;
676    use crate::builtins::ArrayBuiltins;
677    use crate::compute::conformance::cast::test_cast_conformance;
678    use crate::dtype::DType;
679    use crate::dtype::DecimalDType;
680    use crate::dtype::DecimalType;
681    use crate::dtype::Nullability;
682    use crate::dtype::PType;
683    use crate::dtype::i256;
684    use crate::expr::stats::Stat;
685    use crate::validity::Validity;
686
687    #[test]
688    fn cast_u32_u8() {
689        let mut ctx = array_session().create_execution_ctx();
690        let arr = buffer![0u32, 10, 200].into_array();
691
692        // cast from u32 to u8
693        let p = arr
694            .cast(PType::U8.into())
695            .unwrap()
696            .execute::<PrimitiveArray>(&mut ctx)
697            .unwrap();
698        assert_arrays_eq!(p, PrimitiveArray::from_iter([0u8, 10, 200]), &mut ctx);
699        assert!(matches!(p.validity(), Ok(Validity::NonNullable)));
700
701        // to nullable
702        let p = p
703            .into_array()
704            .cast(DType::Primitive(PType::U8, Nullability::Nullable))
705            .unwrap()
706            .execute::<PrimitiveArray>(&mut ctx)
707            .unwrap();
708        assert_arrays_eq!(
709            p,
710            PrimitiveArray::new(buffer![0u8, 10, 200], Validity::AllValid),
711            &mut ctx
712        );
713        assert!(matches!(p.validity(), Ok(Validity::AllValid)));
714
715        // back to non-nullable
716        let p = p
717            .into_array()
718            .cast(DType::Primitive(PType::U8, Nullability::NonNullable))
719            .unwrap()
720            .execute::<PrimitiveArray>(&mut ctx)
721            .unwrap();
722        assert_arrays_eq!(p, PrimitiveArray::from_iter([0u8, 10, 200]), &mut ctx);
723        assert!(matches!(p.validity(), Ok(Validity::NonNullable)));
724
725        // to nullable u32
726        let p = p
727            .into_array()
728            .cast(DType::Primitive(PType::U32, Nullability::Nullable))
729            .unwrap()
730            .execute::<PrimitiveArray>(&mut ctx)
731            .unwrap();
732        assert_arrays_eq!(
733            p,
734            PrimitiveArray::new(buffer![0u32, 10, 200], Validity::AllValid),
735            &mut ctx
736        );
737        assert!(matches!(p.validity(), Ok(Validity::AllValid)));
738
739        // to non-nullable u8
740        let p = p
741            .into_array()
742            .cast(DType::Primitive(PType::U8, Nullability::NonNullable))
743            .unwrap()
744            .execute::<PrimitiveArray>(&mut ctx)
745            .unwrap();
746        assert_arrays_eq!(p, PrimitiveArray::from_iter([0u8, 10, 200]), &mut ctx);
747        assert!(matches!(p.validity(), Ok(Validity::NonNullable)));
748    }
749
750    #[test]
751    fn cast_u32_f32() {
752        let mut ctx = array_session().create_execution_ctx();
753        let arr = buffer![0u32, 10, 200].into_array();
754        let u8arr = arr.cast(PType::F32.into()).unwrap();
755        assert_arrays_eq!(
756            u8arr,
757            PrimitiveArray::from_iter([0.0f32, 10., 200.]),
758            &mut ctx
759        );
760    }
761
762    #[test]
763    fn cast_integer_to_decimal_rescales() -> VortexResult<()> {
764        let mut ctx = array_session().create_execution_ctx();
765        let decimal_dtype = DecimalDType::new(5, 2);
766        let casted = PrimitiveArray::from_iter([42i32, -7])
767            .into_array()
768            .cast(DType::Decimal(decimal_dtype, Nullability::NonNullable))?
769            .execute::<DecimalArray>(&mut ctx)?;
770
771        assert_eq!(
772            casted.dtype(),
773            &DType::Decimal(decimal_dtype, Nullability::NonNullable)
774        );
775        assert_eq!(casted.values_type(), DecimalType::I32);
776        assert_eq!(casted.buffer::<i32>().as_ref(), &[4_200, -700]);
777        Ok(())
778    }
779
780    #[test]
781    fn cast_same_width_signed_integer_to_decimal_reuses_buffer() -> VortexResult<()> {
782        let mut ctx = array_session().create_execution_ctx();
783        let source = PrimitiveArray::from_iter([42i32, -7]);
784        let source_ptr = source.as_slice::<i32>().as_ptr();
785        let casted = source
786            .into_array()
787            .cast(DType::Decimal(
788                DecimalDType::new(9, 0),
789                Nullability::NonNullable,
790            ))?
791            .execute::<DecimalArray>(&mut ctx)?;
792
793        assert_eq!(casted.buffer::<i32>().as_ptr(), source_ptr);
794        assert_eq!(casted.buffer::<i32>().as_ref(), &[42, -7]);
795        assert_arrays_eq!(
796            &casted,
797            DecimalArray::new(
798                buffer![42i32, -7],
799                DecimalDType::new(9, 0),
800                Validity::NonNullable
801            ),
802            &mut ctx
803        );
804        Ok(())
805    }
806
807    #[test]
808    fn cast_same_width_signed_integer_to_decimal_reuses_buffer_with_cached_bounds()
809    -> VortexResult<()> {
810        let mut ctx = array_session().create_execution_ctx();
811        let source = PrimitiveArray::from_iter([42i32, -7]);
812        let source_ptr = source.as_slice::<i32>().as_ptr();
813        let source = source.into_array();
814        source
815            .statistics()
816            .compute_all(&[Stat::Min, Stat::Max], &mut ctx)?;
817        let casted = source
818            .cast(DType::Decimal(
819                DecimalDType::new(9, 0),
820                Nullability::NonNullable,
821            ))?
822            .execute::<DecimalArray>(&mut ctx)?;
823
824        assert_eq!(casted.buffer::<i32>().as_ptr(), source_ptr);
825        assert_arrays_eq!(
826            &casted,
827            DecimalArray::new(
828                buffer![42i32, -7],
829                DecimalDType::new(9, 0),
830                Validity::NonNullable
831            ),
832            &mut ctx
833        );
834        Ok(())
835    }
836
837    #[test]
838    fn cast_same_width_signed_integer_to_decimal_ignores_out_of_range_nulls() -> VortexResult<()> {
839        let mut ctx = array_session().create_execution_ctx();
840        let source = PrimitiveArray::new(buffer![i32::MAX, 42], Validity::from_iter([false, true]));
841        let source_ptr = source.as_slice::<i32>().as_ptr();
842        let casted = source
843            .into_array()
844            .cast(DType::Decimal(
845                DecimalDType::new(9, 0),
846                Nullability::Nullable,
847            ))?
848            .execute::<DecimalArray>(&mut ctx)?;
849
850        assert_eq!(casted.buffer::<i32>().as_ptr(), source_ptr);
851        assert_eq!(
852            casted.validity()?.execute_mask(casted.len(), &mut ctx)?,
853            Mask::from(BitBuffer::from(vec![false, true]))
854        );
855        assert_arrays_eq!(
856            &casted,
857            DecimalArray::from_option_iter([None, Some(42i32)], DecimalDType::new(9, 0)),
858            &mut ctx
859        );
860        Ok(())
861    }
862
863    #[test]
864    fn cast_same_width_signed_integer_to_decimal_checks_precision() -> VortexResult<()> {
865        let mut ctx = array_session().create_execution_ctx();
866        let casted = PrimitiveArray::from_iter([i32::MAX])
867            .into_array()
868            .cast(DType::Decimal(
869                DecimalDType::new(9, 0),
870                Nullability::NonNullable,
871            ))?;
872
873        let error = casted.execute::<DecimalArray>(&mut ctx).unwrap_err();
874        assert!(error.to_string().contains("does not fit in precision"));
875        Ok(())
876    }
877
878    #[test]
879    fn cast_u64_to_decimal() -> VortexResult<()> {
880        let mut ctx = array_session().create_execution_ctx();
881        let decimal_dtype = DecimalDType::new(20, 0);
882        let casted = PrimitiveArray::from_iter([u64::MAX])
883            .into_array()
884            .cast(DType::Decimal(decimal_dtype, Nullability::NonNullable))?
885            .execute::<DecimalArray>(&mut ctx)?;
886
887        assert_eq!(
888            casted.dtype(),
889            &DType::Decimal(decimal_dtype, Nullability::NonNullable)
890        );
891        assert_eq!(casted.values_type(), DecimalType::I128);
892        assert_eq!(casted.buffer::<i128>().as_ref(), &[i128::from(u64::MAX)]);
893        Ok(())
894    }
895
896    #[test]
897    fn cast_integer_to_decimal_reports_scale_up_overflow() -> VortexResult<()> {
898        let mut ctx = array_session().create_execution_ctx();
899        let dtype = DType::Decimal(DecimalDType::new(38, 20), Nullability::NonNullable);
900        let casted = PrimitiveArray::from_iter([u64::MAX])
901            .into_array()
902            .cast(dtype)?;
903        let actual = casted.execute::<DecimalArray>(&mut ctx).unwrap_err();
904
905        assert!(
906            actual
907                .to_string()
908                .contains("does not fit in precision of decimal(38,20)")
909        );
910        Ok(())
911    }
912
913    #[test]
914    fn cast_u8_to_negative_scale_decimal() -> VortexResult<()> {
915        let mut ctx = array_session().create_execution_ctx();
916        let decimal_dtype = DecimalDType::new(2, -2);
917        let casted = PrimitiveArray::from_iter([200u8])
918            .into_array()
919            .cast(DType::Decimal(decimal_dtype, Nullability::NonNullable))?
920            .execute::<DecimalArray>(&mut ctx)?;
921
922        assert_eq!(
923            casted.dtype(),
924            &DType::Decimal(decimal_dtype, Nullability::NonNullable)
925        );
926        assert_eq!(casted.values_type(), DecimalType::I8);
927        assert_eq!(casted.buffer::<i8>().as_ref(), &[2]);
928        Ok(())
929    }
930
931    #[test]
932    fn cast_u64_to_negative_scale_decimal() -> VortexResult<()> {
933        let mut ctx = array_session().create_execution_ctx();
934        let decimal_dtype = DecimalDType::new(1, -19);
935        let casted = PrimitiveArray::from_iter([10_000_000_000_000_000_000u64])
936            .into_array()
937            .cast(DType::Decimal(decimal_dtype, Nullability::NonNullable))?
938            .execute::<DecimalArray>(&mut ctx)?;
939
940        assert_eq!(
941            casted.dtype(),
942            &DType::Decimal(decimal_dtype, Nullability::NonNullable)
943        );
944        assert_eq!(casted.values_type(), DecimalType::I8);
945        assert_eq!(casted.buffer::<i8>().as_ref(), &[1]);
946        Ok(())
947    }
948
949    #[test]
950    fn cast_integer_to_i256_decimal() -> VortexResult<()> {
951        let mut ctx = array_session().create_execution_ctx();
952        let decimal_dtype = DecimalDType::new(39, 2);
953        let casted = PrimitiveArray::from_iter([42i64])
954            .into_array()
955            .cast(DType::Decimal(decimal_dtype, Nullability::NonNullable))?
956            .execute::<DecimalArray>(&mut ctx)?;
957
958        assert_eq!(
959            casted.dtype(),
960            &DType::Decimal(decimal_dtype, Nullability::NonNullable)
961        );
962        assert_eq!(casted.values_type(), DecimalType::I256);
963        assert_eq!(casted.buffer::<i256>().as_ref(), &[i256::from_i128(4_200)]);
964        Ok(())
965    }
966
967    #[test]
968    fn cast_all_null_integer_to_decimal() -> VortexResult<()> {
969        let mut ctx = array_session().create_execution_ctx();
970        let decimal_dtype = DecimalDType::new(39, 2);
971        let casted = PrimitiveArray::new(buffer![i64::MAX, i64::MIN], Validity::AllInvalid)
972            .into_array()
973            .cast(DType::Decimal(decimal_dtype, Nullability::Nullable))?
974            .execute::<DecimalArray>(&mut ctx)?;
975
976        assert!(matches!(casted.validity(), Ok(Validity::AllInvalid)));
977        assert_arrays_eq!(
978            &casted,
979            DecimalArray::from_option_iter([None::<i256>; 2], decimal_dtype),
980            &mut ctx
981        );
982        Ok(())
983    }
984
985    #[test]
986    fn cast_integer_to_negative_scale_decimal_requires_exact_rescale() -> VortexResult<()> {
987        let mut ctx = array_session().create_execution_ctx();
988        let decimal_dtype = DecimalDType::new(3, -2);
989        let casted = PrimitiveArray::from_iter([1_200i32, -500])
990            .into_array()
991            .cast(DType::Decimal(decimal_dtype, Nullability::NonNullable))?
992            .execute::<DecimalArray>(&mut ctx)?;
993
994        assert_eq!(
995            casted.dtype(),
996            &DType::Decimal(decimal_dtype, Nullability::NonNullable)
997        );
998        assert_eq!(casted.buffer::<i16>().as_ref(), &[12, -5]);
999
1000        let error = PrimitiveArray::from_iter([42i32])
1001            .into_array()
1002            .cast(DType::Decimal(decimal_dtype, Nullability::NonNullable))?
1003            .execute::<DecimalArray>(&mut ctx)
1004            .unwrap_err();
1005        assert!(error.to_string().contains("would lose precision"));
1006        Ok(())
1007    }
1008
1009    #[test]
1010    fn cast_zero_to_large_negative_scale_decimal() -> VortexResult<()> {
1011        let mut ctx = array_session().create_execution_ctx();
1012        let decimal_dtype = DecimalDType::new(3, -128);
1013        let casted = PrimitiveArray::from_iter([0i32])
1014            .into_array()
1015            .cast(DType::Decimal(decimal_dtype, Nullability::NonNullable))?
1016            .execute::<DecimalArray>(&mut ctx)?;
1017
1018        assert_eq!(
1019            casted.dtype(),
1020            &DType::Decimal(decimal_dtype, Nullability::NonNullable)
1021        );
1022        assert_eq!(casted.buffer::<i16>().as_ref(), &[0]);
1023        Ok(())
1024    }
1025
1026    #[test]
1027    fn cast_integer_to_decimal_ignores_out_of_range_null_lanes() -> VortexResult<()> {
1028        let mut ctx = array_session().create_execution_ctx();
1029        let decimal_dtype = DecimalDType::new(3, 1);
1030        let casted = PrimitiveArray::new(buffer![999i32, 42], Validity::from_iter([false, true]))
1031            .into_array()
1032            .cast(DType::Decimal(decimal_dtype, Nullability::Nullable))?
1033            .execute::<DecimalArray>(&mut ctx)?;
1034
1035        assert_eq!(
1036            casted.dtype(),
1037            &DType::Decimal(decimal_dtype, Nullability::Nullable)
1038        );
1039        assert_eq!(casted.buffer::<i16>().as_ref()[1], 420);
1040        assert_eq!(
1041            casted.validity()?.execute_mask(casted.len(), &mut ctx)?,
1042            Mask::from(BitBuffer::from(vec![false, true]))
1043        );
1044        Ok(())
1045    }
1046
1047    #[test]
1048    fn cast_integer_to_decimal_checks_precision() -> VortexResult<()> {
1049        let mut ctx = array_session().create_execution_ctx();
1050        let casted = PrimitiveArray::from_iter([100i32])
1051            .into_array()
1052            .cast(DType::Decimal(
1053                DecimalDType::new(2, 1),
1054                Nullability::NonNullable,
1055            ))?;
1056
1057        let error = casted.execute::<DecimalArray>(&mut ctx).unwrap_err();
1058        assert!(error.to_string().contains("does not fit in precision"));
1059        Ok(())
1060    }
1061
1062    #[test]
1063    fn cast_floating_primitive_to_decimal_fails() -> VortexResult<()> {
1064        let mut ctx = array_session().create_execution_ctx();
1065        let casted = PrimitiveArray::from_iter([1.0f64])
1066            .into_array()
1067            .cast(DType::Decimal(
1068                DecimalDType::new(3, 1),
1069                Nullability::NonNullable,
1070            ))?;
1071
1072        let error = casted.execute::<DecimalArray>(&mut ctx).unwrap_err();
1073        assert!(
1074            error
1075                .to_string()
1076                .contains("Cannot cast floating primitive f64 to decimal decimal(3,1)")
1077        );
1078        Ok(())
1079    }
1080
1081    #[test]
1082    fn cast_i32_u32() {
1083        let arr = buffer![-1i32].into_array();
1084        #[expect(deprecated)]
1085        let error = arr
1086            .cast(PType::U32.into())
1087            .and_then(|a| a.to_canonical().map(|c| c.into_array()))
1088            .unwrap_err();
1089        assert!(matches!(error, VortexError::Compute(..)));
1090        assert!(error.to_string().contains("values exceed target range"));
1091    }
1092
1093    #[test]
1094    fn cast_array_with_nulls_to_nonnullable() {
1095        let arr = PrimitiveArray::from_option_iter([Some(-1i32), None, Some(10)]);
1096        #[expect(deprecated)]
1097        let err = arr
1098            .into_array()
1099            .cast(PType::I32.into())
1100            .and_then(|a| a.to_canonical().map(|c| c.into_array()))
1101            .unwrap_err();
1102
1103        assert!(matches!(err, VortexError::InvalidArgument(..)));
1104        assert!(
1105            err.to_string()
1106                .contains("Cannot cast array with invalid values to non-nullable type.")
1107        );
1108    }
1109
1110    #[test]
1111    fn cast_with_invalid_nulls() {
1112        let mut ctx = array_session().create_execution_ctx();
1113        let arr = PrimitiveArray::new(
1114            buffer![-1i32, 0, 10],
1115            Validity::from_iter([false, true, true]),
1116        );
1117        let p = arr
1118            .into_array()
1119            .cast(DType::Primitive(PType::U32, Nullability::Nullable))
1120            .unwrap();
1121        assert_arrays_eq!(
1122            p,
1123            PrimitiveArray::from_option_iter([None, Some(0u32), Some(10)]),
1124            &mut ctx
1125        );
1126        assert_eq!(
1127            p.validity()
1128                .unwrap()
1129                .execute_mask(p.len(), &mut array_session().create_execution_ctx())
1130                .unwrap(),
1131            Mask::from(BitBuffer::from(vec![false, true, true]))
1132        );
1133    }
1134
1135    /// Same-width integer cast where all values fit: should reinterpret the
1136    /// buffer without allocation (pointer identity).
1137    #[test]
1138    fn cast_same_width_int_reinterprets_buffer() -> VortexResult<()> {
1139        let mut ctx = array_session().create_execution_ctx();
1140        let src = PrimitiveArray::from_iter([0u32, 10, 100]);
1141        let src_ptr = src.as_slice::<u32>().as_ptr();
1142
1143        let dst = src
1144            .into_array()
1145            .cast(PType::I32.into())?
1146            .execute::<PrimitiveArray>(&mut ctx)?;
1147        let dst_ptr = dst.as_slice::<i32>().as_ptr();
1148
1149        // Zero-copy: the data pointer should be identical.
1150        assert_eq!(src_ptr as usize, dst_ptr as usize);
1151        assert_arrays_eq!(dst, PrimitiveArray::from_iter([0i32, 10, 100]), &mut ctx);
1152        Ok(())
1153    }
1154
1155    /// Same-width integer cast where values don't fit: should fall through
1156    /// to the allocating path and produce an error.
1157    #[test]
1158    fn cast_same_width_int_out_of_range_errors() {
1159        let arr = buffer![u32::MAX].into_array();
1160        #[expect(deprecated)]
1161        let err = arr
1162            .cast(PType::I32.into())
1163            .and_then(|a| a.to_canonical().map(|c| c.into_array()))
1164            .unwrap_err();
1165        assert!(matches!(err, VortexError::Compute(..)));
1166    }
1167
1168    /// All-null array cast between same-width types should succeed without
1169    /// touching the buffer contents.
1170    #[test]
1171    fn cast_same_width_all_null() -> VortexResult<()> {
1172        let mut ctx = array_session().create_execution_ctx();
1173        let arr = PrimitiveArray::new(buffer![0xFFu8, 0xFF], Validity::AllInvalid);
1174        let casted = arr
1175            .into_array()
1176            .cast(DType::Primitive(PType::I8, Nullability::Nullable))?
1177            .execute::<PrimitiveArray>(&mut ctx)?;
1178        assert_arrays_eq!(
1179            &casted,
1180            PrimitiveArray::from_option_iter([None::<i8>; 2]),
1181            &mut ctx
1182        );
1183        assert!(matches!(casted.validity(), Ok(Validity::AllInvalid)));
1184        Ok(())
1185    }
1186
1187    /// Same-width integer cast with nullable values: out-of-range nulls should
1188    /// not prevent the cast from succeeding.
1189    #[test]
1190    fn cast_same_width_int_nullable_with_out_of_range_nulls() -> VortexResult<()> {
1191        let mut ctx = array_session().create_execution_ctx();
1192        // The null position holds u32::MAX which doesn't fit in i32, but it's
1193        // masked as invalid so the cast should still succeed via reinterpret.
1194        let arr = PrimitiveArray::new(
1195            buffer![u32::MAX, 0u32, 42u32],
1196            Validity::from_iter([false, true, true]),
1197        );
1198        let casted = arr
1199            .into_array()
1200            .cast(DType::Primitive(PType::I32, Nullability::Nullable))?;
1201        assert_arrays_eq!(
1202            casted,
1203            PrimitiveArray::from_option_iter([None, Some(0i32), Some(42)]),
1204            &mut ctx
1205        );
1206        Ok(())
1207    }
1208
1209    #[test]
1210    fn cast_u32_to_u8_with_out_of_range_nulls() -> VortexResult<()> {
1211        let mut ctx = array_session().create_execution_ctx();
1212        let arr = PrimitiveArray::new(
1213            buffer![1000u32, 10u32, 42u32],
1214            Validity::from_iter([false, true, true]),
1215        );
1216        let casted = arr
1217            .into_array()
1218            .cast(DType::Primitive(PType::U8, Nullability::Nullable))?;
1219        assert_arrays_eq!(
1220            casted,
1221            PrimitiveArray::from_option_iter([None, Some(10u8), Some(42)]),
1222            &mut ctx
1223        );
1224        Ok(())
1225    }
1226
1227    #[rstest]
1228    #[case(buffer![0u8, 1, 2, 3, 255].into_array())]
1229    #[case(buffer![0u16, 100, 1000, 65535].into_array())]
1230    #[case(buffer![0u32, 100, 1000, 1000000].into_array())]
1231    #[case(buffer![0u64, 100, 1000, 1000000000].into_array())]
1232    #[case(buffer![-128i8, -1, 0, 1, 127].into_array())]
1233    #[case(buffer![-1000i16, -1, 0, 1, 1000].into_array())]
1234    #[case(buffer![-1000000i32, -1, 0, 1, 1000000].into_array())]
1235    #[case(buffer![-1000000000i64, -1, 0, 1, 1000000000].into_array())]
1236    #[case(buffer![0.0f32, 1.5, -2.5, 100.0, 1e6].into_array())]
1237    #[case(buffer![f32::NAN, f32::INFINITY, f32::NEG_INFINITY, 0.0f32].into_array())]
1238    #[case(buffer![0.0f64, 1.5, -2.5, 100.0, 1e12].into_array())]
1239    #[case(buffer![f64::NAN, f64::INFINITY, f64::NEG_INFINITY, 0.0f64].into_array())]
1240    #[case(PrimitiveArray::from_option_iter([Some(1u8), None, Some(255), Some(0), None]).into_array())]
1241    #[case(PrimitiveArray::from_option_iter([Some(1i32), None, Some(-100), Some(0), None]).into_array())]
1242    #[case(buffer![42u32].into_array())]
1243    fn test_cast_primitive_conformance(#[case] array: ArrayRef) {
1244        test_cast_conformance(&array, &mut array_session().create_execution_ctx());
1245    }
1246}