Skip to main content

vortex_array/arrays/decimal/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::ToPrimitive as NumToPrimitive;
7use vortex_buffer::Buffer;
8use vortex_buffer::BufferMut;
9use vortex_compute::lane_kernels::IndexedSourceExt;
10use vortex_error::VortexError;
11use vortex_error::VortexExpect;
12use vortex_error::VortexResult;
13use vortex_error::vortex_bail;
14use vortex_error::vortex_err;
15use vortex_error::vortex_panic;
16use vortex_mask::Mask;
17
18use crate::ArrayRef;
19use crate::ExecutionCtx;
20use crate::IntoArray;
21use crate::array::ArrayView;
22use crate::arrays::Decimal;
23use crate::arrays::DecimalArray;
24use crate::arrays::PrimitiveArray;
25use crate::arrays::decimal::DecimalArrayExt;
26use crate::dtype::BigCast;
27use crate::dtype::DType;
28use crate::dtype::DecimalDType;
29use crate::dtype::DecimalType;
30use crate::dtype::IntegerPType;
31use crate::dtype::NativeDecimalType;
32use crate::dtype::Nullability;
33use crate::dtype::PType;
34use crate::dtype::ToI256;
35use crate::dtype::i256;
36use crate::match_each_decimal_value_type;
37use crate::match_each_integer_ptype;
38use crate::scalar::DecimalToIntegerCast;
39use crate::scalar::DecimalValue;
40use crate::scalar_fn::fns::cast::CastKernel;
41use crate::scalar_fn::fns::cast::CastReduce;
42use crate::validity::Validity;
43
44impl CastReduce for Decimal {
45    fn cast(array: ArrayView<'_, Decimal>, dtype: &DType) -> VortexResult<Option<ArrayRef>> {
46        // Only nullability changes within the same decimal dtype are reducible without execution.
47        // Precision/scale changes need the kernel.
48        let DType::Decimal(to_decimal_dtype, to_nullability) = dtype else {
49            return Ok(None);
50        };
51        let DType::Decimal(from_decimal_dtype, _) = array.dtype() else {
52            vortex_panic!(
53                "DecimalArray must have decimal dtype, got {:?}",
54                array.dtype()
55            );
56        };
57
58        if from_decimal_dtype != to_decimal_dtype {
59            return Ok(None);
60        }
61
62        let Some(new_validity) = array
63            .validity()?
64            .trivially_cast_nullability(*to_nullability, array.len())?
65        else {
66            return Ok(None);
67        };
68
69        // SAFETY: validity has the same length, only its nullability tag changes.
70        unsafe {
71            Ok(Some(
72                DecimalArray::new_unchecked_handle(
73                    array.buffer_handle().clone(),
74                    array.values_type(),
75                    *to_decimal_dtype,
76                    new_validity,
77                )
78                .into_array(),
79            ))
80        }
81    }
82}
83
84impl CastKernel for Decimal {
85    fn cast(
86        array: ArrayView<'_, Decimal>,
87        dtype: &DType,
88        ctx: &mut ExecutionCtx,
89    ) -> VortexResult<Option<ArrayRef>> {
90        let DType::Decimal(from_decimal_dtype, _) = array.dtype() else {
91            vortex_panic!(
92                "DecimalArray must have decimal dtype, got {:?}",
93                array.dtype()
94            );
95        };
96        if let DType::Primitive(ptype, nullability) = dtype
97            && ptype.is_int()
98        {
99            return match_each_integer_ptype!(*ptype, |T| {
100                cast_to_integer::<T>(array, *nullability, ctx).map(Some)
101            });
102        }
103        if let DType::Primitive(PType::F64, nullability) = dtype {
104            let scale = from_decimal_dtype.scale();
105            return cast_to_f64(array, scale, *nullability, ctx).map(Some);
106        }
107        let DType::Decimal(to_decimal_dtype, to_nullability) = dtype else {
108            return Ok(None);
109        };
110
111        // If the dtype is exactly the same, return self
112        if array.dtype() == dtype {
113            return Ok(Some(array.array().clone()));
114        }
115
116        let validity = array.validity()?;
117
118        // Cast the validity to the new nullability
119        let new_validity = validity
120            .clone()
121            .cast_nullability(*to_nullability, array.len(), ctx)?;
122
123        // Reuse the values buffer untouched when no rescale is required, the target precision
124        // only widens (so every value still fits), and the current physical type is already wide
125        // enough to hold the target precision. This keeps the common precision-widening cast
126        // (and pure nullability changes) zero-copy instead of allocating and re-scanning.
127        if from_decimal_dtype.scale() == to_decimal_dtype.scale()
128            && to_decimal_dtype.precision() >= from_decimal_dtype.precision()
129            && array
130                .values_type()
131                .is_compatible_decimal_value_type(*to_decimal_dtype)
132        {
133            // SAFETY: the source values are bit-identical and remain in range for the wider
134            // precision, and new_validity has the same length, only its nullability tag changes.
135            unsafe {
136                return Ok(Some(
137                    DecimalArray::new_unchecked_handle(
138                        array.buffer_handle().clone(),
139                        array.values_type(),
140                        *to_decimal_dtype,
141                        new_validity,
142                    )
143                    .into_array(),
144                ));
145            }
146        }
147
148        let valid_values = validity.execute_mask(array.len(), ctx)?;
149        let target_values_type = DecimalType::smallest_decimal_value_type(to_decimal_dtype);
150
151        match_each_decimal_value_type!(array.values_type(), |F| {
152            match_each_decimal_value_type!(target_values_type, |T| {
153                cast_decimal_values::<F, T>(
154                    array,
155                    *from_decimal_dtype,
156                    *to_decimal_dtype,
157                    new_validity,
158                    &valid_values,
159                )
160                .map(Some)
161            })
162        })
163    }
164}
165
166fn cast_to_integer<T: IntegerPType + BigCast>(
167    array: ArrayView<'_, Decimal>,
168    nullability: Nullability,
169    ctx: &mut ExecutionCtx,
170) -> VortexResult<ArrayRef> {
171    let source_validity = array.validity()?;
172    let mask = source_validity.execute_mask(array.len(), ctx)?;
173    let validity = source_validity.cast_nullability(nullability, array.len(), ctx)?;
174    let cast = DecimalToIntegerCast::<T>::new(array.decimal_dtype().scale());
175    let buffer = match_each_decimal_value_type!(array.values_type(), |F| {
176        let values = array.buffer::<F>();
177        cast_decimal_buffer(values.as_slice(), &mask, |value: F| {
178            cast.cast(value.to_i256()?)
179        })
180        .map_err(|index| cast.error(DecimalValue::from(values[index]).as_i256()))?
181    });
182    Ok(PrimitiveArray::new(buffer, validity).into_array())
183}
184
185fn cast_to_f64(
186    array: ArrayView<'_, Decimal>,
187    scale: i8,
188    nullability: Nullability,
189    ctx: &mut ExecutionCtx,
190) -> VortexResult<ArrayRef> {
191    let source_validity = array.validity()?;
192    let n = array.len();
193    let mask = source_validity.execute_mask(n, ctx)?;
194    let validity = source_validity.cast_nullability(nullability, n, ctx)?;
195
196    let scale: i32 = scale.as_();
197    let inv_factor: f64 = 10f64.powi(-scale);
198
199    let buffer = match &mask {
200        Mask::AllFalse(_) => BufferMut::<f64>::zeroed(n),
201        Mask::AllTrue(_) => match_each_decimal_value_type!(array.values_type(), |F| {
202            let values = array.buffer::<F>();
203            let values = values.as_slice();
204            let mut out = BufferMut::<f64>::with_capacity(n);
205            values.map_into(&mut out.spare_capacity_mut()[..n], |v: F| {
206                to_f64_lossy::<F>(v) * inv_factor
207            });
208            // SAFETY: map_into wrote every lane before returning.
209            unsafe { out.set_len(n) };
210            out
211        }),
212        Mask::Values(mask_values) => match_each_decimal_value_type!(array.values_type(), |F| {
213            let values = array.buffer::<F>();
214            let values = values.as_slice();
215            let mut out = BufferMut::<f64>::with_capacity(n);
216            let write_result = values.try_map_masked_into(
217                mask_values.bit_buffer(),
218                &mut out.spare_capacity_mut()[..n],
219                |v: F| Some(to_f64_lossy::<F>(v) * inv_factor),
220            );
221            debug_assert!(write_result.is_ok());
222            // SAFETY: try_map_masked_into wrote every lane before returning Ok.
223            unsafe { out.set_len(n) };
224            out
225        }),
226    };
227
228    Ok(PrimitiveArray::new(buffer, validity).into_array())
229}
230
231#[inline]
232fn to_f64_lossy<F: NativeDecimalType>(value: F) -> f64 {
233    NumToPrimitive::to_f64(&value).unwrap_or(f64::NAN)
234}
235
236fn cast_decimal_values<F, T>(
237    array: ArrayView<'_, Decimal>,
238    from_decimal_dtype: DecimalDType,
239    to_decimal_dtype: DecimalDType,
240    validity: Validity,
241    valid_values: &Mask,
242) -> VortexResult<ArrayRef>
243where
244    F: NativeDecimalType,
245    T: NativeDecimalType + CheckedMul,
246    DecimalValue: From<F>,
247{
248    let values = array.buffer::<F>();
249    let values = values.as_slice();
250    let cast_result = match DecimalCastPlan::<T>::new(from_decimal_dtype, to_decimal_dtype) {
251        DecimalCastPlan::SameScale { min, max } => {
252            cast_decimal_buffer(values, valid_values, |value| {
253                let value = <T as BigCast>::from(value)?;
254                (value >= min && value <= max).then_some(value)
255            })
256        }
257        cast_plan => cast_decimal_buffer(values, valid_values, |value| cast_plan.cast(value)),
258    };
259    let buffer = cast_result.map_err(|idx| {
260        decimal_cast_error::<F, T>(values[idx], from_decimal_dtype, to_decimal_dtype)
261    })?;
262
263    Ok(DecimalArray::new(buffer, to_decimal_dtype, validity).into_array())
264}
265
266fn cast_decimal_buffer<F, T>(
267    values: &[F],
268    valid_values: &Mask,
269    cast: impl Fn(F) -> Option<T>,
270) -> Result<Buffer<T>, usize>
271where
272    F: NativeDecimalType,
273    T: Copy + Default,
274{
275    let mut buffer = BufferMut::<T>::with_capacity(values.len());
276    match valid_values {
277        Mask::AllTrue(_) => {
278            values.try_map_into(&mut buffer.spare_capacity_mut()[..values.len()], &cast)?;
279        }
280        Mask::AllFalse(_) => return Ok(BufferMut::<T>::zeroed(values.len()).freeze()),
281        Mask::Values(mask) => {
282            values.try_map_masked_into(
283                mask.bit_buffer(),
284                &mut buffer.spare_capacity_mut()[..values.len()],
285                &cast,
286            )?;
287        }
288    }
289    // SAFETY: the selected map kernel initialized every lane before returning Ok.
290    unsafe { buffer.set_len(values.len()) };
291    Ok(buffer.freeze())
292}
293
294#[cold]
295fn decimal_cast_error<F, T>(
296    value: F,
297    from_decimal_dtype: DecimalDType,
298    to_decimal_dtype: DecimalDType,
299) -> VortexError
300where
301    F: NativeDecimalType,
302    T: NativeDecimalType,
303    DecimalValue: From<F>,
304{
305    match DecimalValue::from(value)
306        .cast_decimal(from_decimal_dtype, to_decimal_dtype)
307        .and_then(|value| {
308            value.cast::<T>().ok_or_else(|| {
309                vortex_err!(
310                    "decimal value cannot be represented as {} after casting to {}",
311                    T::DECIMAL_TYPE,
312                    to_decimal_dtype
313                )
314            })
315        }) {
316        Ok(_) => {
317            // The fast path only returns `None` for values the slow path also rejects, so this
318            // arm should be unreachable. If it is hit, the fast and slow paths have drifted and
319            // we are erroring on a value that is actually representable.
320            debug_assert!(
321                false,
322                "decimal fast-path cast rejected value {value} that the slow path accepts \
323                 (from {from_decimal_dtype} to {to_decimal_dtype})"
324            );
325            vortex_err!(
326                "decimal value cannot be represented as {} after casting from {} to {}",
327                T::DECIMAL_TYPE,
328                from_decimal_dtype,
329                to_decimal_dtype
330            )
331        }
332        Err(error) => error,
333    }
334}
335
336#[derive(Debug, Clone, Copy)]
337enum DecimalCastPlan<T> {
338    SameScale { min: T, max: T },
339    ScaleUp { factor: T, min: T, max: T },
340    ScaleUpOverflow,
341    ScaleDown { factor: i256, min: i256, max: i256 },
342    ScaleDownOverflow,
343}
344
345impl<T> DecimalCastPlan<T>
346where
347    T: NativeDecimalType + CheckedMul,
348{
349    fn new(from_decimal_dtype: DecimalDType, to_decimal_dtype: DecimalDType) -> Self {
350        let scale_delta = to_decimal_dtype.scale() as i16 - from_decimal_dtype.scale() as i16;
351        if scale_delta == 0 {
352            let (min, max) = decimal_precision_range::<T>(to_decimal_dtype);
353            return Self::SameScale { min, max };
354        }
355
356        if scale_delta > 0 {
357            let Some(factor) = decimal_scale_factor::<T>(scale_delta as u32) else {
358                return Self::ScaleUpOverflow;
359            };
360            let (min, max) = decimal_precision_range::<T>(to_decimal_dtype);
361            return Self::ScaleUp { factor, min, max };
362        }
363
364        let Some(factor) = decimal_scale_factor::<i256>((-scale_delta) as u32) else {
365            return Self::ScaleDownOverflow;
366        };
367        let (min, max) = decimal_precision_range::<i256>(to_decimal_dtype);
368        Self::ScaleDown { factor, min, max }
369    }
370
371    #[inline]
372    fn cast<F>(&self, value: F) -> Option<T>
373    where
374        F: NativeDecimalType,
375    {
376        match *self {
377            DecimalCastPlan::SameScale { min, max } => {
378                let value = <T as BigCast>::from(value)?;
379                (value >= min && value <= max).then_some(value)
380            }
381            DecimalCastPlan::ScaleUp { factor, min, max } => {
382                let value = <T as BigCast>::from(value)?;
383                let value = value.checked_mul(&factor)?;
384                (value >= min && value <= max).then_some(value)
385            }
386            DecimalCastPlan::ScaleUpOverflow | DecimalCastPlan::ScaleDownOverflow => {
387                (value == F::default()).then_some(T::default())
388            }
389            DecimalCastPlan::ScaleDown { factor, min, max } => {
390                let value = <i256 as BigCast>::from(value)?;
391                if value == i256::ZERO {
392                    return Some(T::default());
393                }
394                if value % factor != i256::ZERO {
395                    return None;
396                }
397
398                let value = value / factor;
399                if value < min || value > max {
400                    return None;
401                }
402                <T as BigCast>::from(value)
403            }
404        }
405    }
406}
407
408fn decimal_precision_range<T: NativeDecimalType>(decimal_dtype: DecimalDType) -> (T, T) {
409    let precision = usize::from(decimal_dtype.precision());
410    (
411        T::MIN_BY_PRECISION[precision],
412        T::MAX_BY_PRECISION[precision],
413    )
414}
415
416fn decimal_scale_factor<T>(exp: u32) -> Option<T>
417where
418    T: NativeDecimalType + CheckedMul,
419{
420    let ten = <T as BigCast>::from(10_i8)?;
421    let mut factor = <T as BigCast>::from(1_i8)?;
422    for _ in 0..exp {
423        factor = factor.checked_mul(&ten)?;
424    }
425    Some(factor)
426}
427
428/// Upcast a DecimalArray to a wider physical representation (e.g., i32 -> i64) while keeping
429/// the same precision and scale.
430///
431/// This is useful when you need to widen the underlying storage type to accommodate operations
432/// that might overflow the current representation, or to match the physical type expected by
433/// downstream consumers.
434///
435/// # Errors
436///
437/// Returns an error if `to_values_type` is narrower than the array's current values type.
438/// Only upcasting (widening) is supported.
439pub fn upcast_decimal_values(
440    array: ArrayView<'_, Decimal>,
441    to_values_type: DecimalType,
442) -> VortexResult<DecimalArray> {
443    let from_values_type = array.values_type();
444
445    // If already the target type, just clone
446    if from_values_type == to_values_type {
447        return Ok(array.array().as_::<Decimal>().into_owned());
448    }
449
450    // Only allow upcasting (widening)
451    if to_values_type < from_values_type {
452        vortex_bail!(
453            "Cannot downcast decimal values from {:?} to {:?}. Only upcasting is supported.",
454            from_values_type,
455            to_values_type
456        );
457    }
458
459    let decimal_dtype = array.decimal_dtype();
460    let validity = array.validity()?;
461
462    // Use match_each_decimal_value_type to dispatch based on source and target types
463    match_each_decimal_value_type!(from_values_type, |F| {
464        let from_buffer = array.buffer::<F>();
465        match_each_decimal_value_type!(to_values_type, |T| {
466            let to_buffer = upcast_decimal_buffer::<F, T>(from_buffer);
467            Ok(DecimalArray::new(to_buffer, decimal_dtype, validity))
468        })
469    })
470}
471
472/// Upcast a buffer of decimal values from type F to type T.
473/// Since T is wider than F, this conversion never fails.
474fn upcast_decimal_buffer<F: NativeDecimalType, T: NativeDecimalType>(from: Buffer<F>) -> Buffer<T> {
475    from.iter()
476        .map(|&v| T::from(v).vortex_expect("upcast should never fail"))
477        .collect()
478}
479
480#[cfg(test)]
481mod tests {
482    use rstest::rstest;
483    use vortex_buffer::buffer;
484    use vortex_error::VortexResult;
485
486    use super::upcast_decimal_values;
487    use crate::Canonical;
488    use crate::IntoArray;
489    use crate::VortexSessionExecute;
490    use crate::array_session;
491    use crate::arrays::DecimalArray;
492    use crate::arrays::PrimitiveArray;
493    use crate::assert_arrays_eq;
494    use crate::builtins::ArrayBuiltins;
495    use crate::compute::conformance::cast::test_cast_conformance;
496    use crate::dtype::BigCast;
497    use crate::dtype::DType;
498    use crate::dtype::DecimalDType;
499    use crate::dtype::DecimalType;
500    use crate::dtype::Nullability;
501    use crate::dtype::PType;
502    use crate::dtype::i256;
503    use crate::match_each_decimal_value_type;
504    use crate::match_each_integer_ptype;
505    use crate::scalar::Scalar;
506    use crate::validity::Validity;
507
508    #[rstest]
509    #[case::workflow(0, vec![0, 1], vec![0, 1])]
510    #[case::fractional(1, vec![-199, -1, 0, 1, 199], vec![-19, 0, 0, 0, 19])]
511    #[case::negative_scale(-2, vec![-12, 0, 12], vec![-1200, 0, 1200])]
512    #[case::large_exact(
513        0,
514        vec![1_786_639_777_684_000_001],
515        vec![1_786_639_777_684_000_001]
516    )]
517    #[case::large_scale(76, vec![-1, 1], vec![0, 0])]
518    #[case::zero_extreme_scale(-128, vec![0, 0], vec![0, 0])]
519    fn cast_decimal_to_integer_policy(
520        #[case] scale: i8,
521        #[case] values: Vec<i128>,
522        #[case] expected: Vec<i64>,
523    ) -> VortexResult<()> {
524        let mut ctx = array_session().create_execution_ctx();
525        let decimal_dtype = DecimalDType::new(76, scale);
526        let array = DecimalArray::new(
527            values.iter().copied().map(i256::from_i128).collect(),
528            decimal_dtype,
529            Validity::NonNullable,
530        );
531        let target = DType::Primitive(PType::I64, Nullability::NonNullable);
532        let casted = array
533            .into_array()
534            .cast(target.clone())?
535            .execute::<PrimitiveArray>(&mut ctx)?;
536        assert_arrays_eq!(
537            casted,
538            PrimitiveArray::from_iter(expected.iter().copied()),
539            &mut ctx
540        );
541        for (value, expected) in values.into_iter().zip(expected) {
542            let scalar = Scalar::decimal(value.into(), decimal_dtype, Nullability::NonNullable);
543            assert_eq!(scalar.cast(&target)?, Scalar::from(expected));
544        }
545        Ok(())
546    }
547
548    #[rstest]
549    fn cast_decimal_to_integer_storage_and_nulls(
550        #[values(
551            DecimalType::I8,
552            DecimalType::I16,
553            DecimalType::I32,
554            DecimalType::I64,
555            DecimalType::I128,
556            DecimalType::I256
557        )]
558        storage: DecimalType,
559        #[values(
560            PType::I8, PType::I16, PType::I32, PType::I64, PType::U8, PType::U16, PType::U32,
561            PType::U64
562        )]
563        target: PType,
564    ) -> VortexResult<()> {
565        let mut ctx = array_session().create_execution_ctx();
566        let decimal_dtype = DecimalDType::new(3, 1);
567        let array = match_each_decimal_value_type!(storage, |F| {
568            DecimalArray::from_option_iter(
569                [Some(19i8), None, Some(123)].map(|value| value.and_then(<F as BigCast>::from)),
570                decimal_dtype,
571            )
572        });
573        let dtype = DType::Primitive(target, Nullability::Nullable);
574        let casted = array
575            .into_array()
576            .cast(dtype.clone())?
577            .execute::<PrimitiveArray>(&mut ctx)?;
578        match_each_integer_ptype!(target, |T| {
579            assert_arrays_eq!(
580                casted,
581                PrimitiveArray::from_option_iter([Some(1 as T), None, Some(12 as T)]),
582                &mut ctx
583            );
584            for (value, expected) in [(19i8, 1 as T), (123, 12 as T)] {
585                let scalar = Scalar::decimal(value.into(), decimal_dtype, Nullability::Nullable);
586                assert_eq!(
587                    scalar.cast(&dtype)?,
588                    Scalar::primitive(expected, Nullability::Nullable)
589                );
590            }
591        });
592        Ok(())
593    }
594
595    #[rstest]
596    fn cast_decimal_to_integer_bounds(
597        #[values(
598            PType::I8, PType::I16, PType::I32, PType::I64, PType::U8, PType::U16, PType::U32,
599            PType::U64
600        )]
601        target: PType,
602    ) -> VortexResult<()> {
603        let mut ctx = array_session().create_execution_ctx();
604        let decimal_dtype = DecimalDType::new(76, 1);
605        let dtype = DType::Primitive(target, Nullability::NonNullable);
606        match_each_integer_ptype!(target, |T| {
607            let ten = i256::from_i128(10);
608            let minimum = i256::from_i128(T::MIN as i128) * ten;
609            let maximum = i256::from_i128(T::MAX as i128) * ten;
610            let array = DecimalArray::new(
611                buffer![minimum, maximum],
612                decimal_dtype,
613                Validity::NonNullable,
614            );
615            let casted = array
616                .into_array()
617                .cast(dtype.clone())?
618                .execute::<PrimitiveArray>(&mut ctx)?;
619            assert_arrays_eq!(
620                casted,
621                PrimitiveArray::from_iter([T::MIN, T::MAX]),
622                &mut ctx
623            );
624            for (value, expected) in [(minimum, T::MIN), (maximum, T::MAX)] {
625                let scalar = Scalar::decimal(value.into(), decimal_dtype, Nullability::NonNullable);
626                assert_eq!(
627                    scalar.cast(&dtype)?,
628                    Scalar::primitive(expected, Nullability::NonNullable)
629                );
630            }
631            // The scalar policy rejects values outside the range even when truncation would fit.
632            for value in [
633                minimum - i256::ONE,
634                maximum + i256::ONE,
635                minimum - ten,
636                maximum + ten,
637            ] {
638                let array = DecimalArray::new(
639                    buffer![i256::ZERO, value],
640                    decimal_dtype,
641                    Validity::NonNullable,
642                );
643                let error = array
644                    .into_array()
645                    .cast(dtype.clone())?
646                    .execute::<PrimitiveArray>(&mut ctx)
647                    .unwrap_err();
648                assert!(error.to_string().contains("out of range"));
649                let scalar = Scalar::decimal(value.into(), decimal_dtype, Nullability::NonNullable);
650                assert!(scalar.cast(&dtype).is_err());
651            }
652        });
653        Ok(())
654    }
655
656    #[test]
657    fn cast_decimal_to_integer_masks_and_empty() -> VortexResult<()> {
658        let mut ctx = array_session().create_execution_ctx();
659        let decimal_dtype = DecimalDType::new(21, 0);
660        let target = DType::Primitive(PType::I8, Nullability::Nullable);
661        for validity in [Validity::from_iter([false, true]), Validity::AllInvalid] {
662            let expected = PrimitiveArray::new(buffer![0i8, 12], validity.clone());
663            let array = DecimalArray::new(buffer![999i128, 12], decimal_dtype, validity);
664            let casted = array
665                .into_array()
666                .cast(target.clone())?
667                .execute::<PrimitiveArray>(&mut ctx)?;
668            assert_arrays_eq!(casted, expected, &mut ctx);
669        }
670        let array = DecimalArray::new(
671            buffer![999i128, 12],
672            decimal_dtype,
673            Validity::from_iter([false, true]),
674        );
675        assert!(
676            array
677                .into_array()
678                .cast(target.as_nonnullable())?
679                .execute::<PrimitiveArray>(&mut ctx)
680                .is_err()
681        );
682        let empty = DecimalArray::from_option_iter([] as [Option<i128>; 0], decimal_dtype);
683        let casted = empty
684            .into_array()
685            .cast(target.as_nonnullable())?
686            .execute::<PrimitiveArray>(&mut ctx)?;
687        assert_arrays_eq!(casted, PrimitiveArray::from_iter([] as [i8; 0]), &mut ctx);
688        Ok(())
689    }
690
691    #[test]
692    fn cast_decimal_to_integer_wide_storage() -> VortexResult<()> {
693        let mut ctx = array_session().create_execution_ctx();
694        let factor = i256::from_i128(10).checked_pow(40).unwrap();
695        let value = i256::from_i128(123) * factor + i256::ONE;
696        let decimal_dtype = DecimalDType::new(76, 40);
697        let target = DType::Primitive(PType::I64, Nullability::NonNullable);
698        let array = DecimalArray::new(buffer![value, -value], decimal_dtype, Validity::NonNullable);
699        let casted = array
700            .into_array()
701            .cast(target.clone())?
702            .execute::<PrimitiveArray>(&mut ctx)?;
703        assert_arrays_eq!(casted, PrimitiveArray::from_iter([123i64, -123]), &mut ctx);
704        for (value, expected) in [(value, 123i64), (-value, -123)] {
705            let scalar = Scalar::decimal(value.into(), decimal_dtype, Nullability::NonNullable);
706            assert_eq!(scalar.cast(&target)?, Scalar::from(expected));
707        }
708        let huge = DecimalArray::new(
709            buffer![i256::ONE],
710            DecimalDType::new(1, -128),
711            Validity::NonNullable,
712        );
713        assert!(
714            huge.into_array()
715                .cast(target)?
716                .execute::<PrimitiveArray>(&mut ctx)
717                .is_err()
718        );
719        Ok(())
720    }
721
722    #[test]
723    fn cast_decimal_to_nullable() {
724        let mut ctx = array_session().create_execution_ctx();
725        let decimal_dtype = DecimalDType::new(10, 2);
726        let array = DecimalArray::new(
727            buffer![100i32, 200, 300],
728            decimal_dtype,
729            Validity::NonNullable,
730        );
731
732        // Cast to nullable
733        let nullable_dtype = DType::Decimal(decimal_dtype, Nullability::Nullable);
734        let casted = array
735            .into_array()
736            .cast(nullable_dtype.clone())
737            .unwrap()
738            .execute::<DecimalArray>(&mut ctx)
739            .unwrap();
740
741        assert_eq!(casted.dtype(), &nullable_dtype);
742        assert!(matches!(casted.validity(), Ok(Validity::AllValid)));
743        assert_eq!(casted.len(), 3);
744    }
745
746    #[test]
747    fn cast_nullable_to_non_nullable() {
748        let mut ctx = array_session().create_execution_ctx();
749        let decimal_dtype = DecimalDType::new(10, 2);
750
751        // Create nullable array with no nulls
752        let array = DecimalArray::new(buffer![100i32, 200, 300], decimal_dtype, Validity::AllValid);
753
754        // Cast to non-nullable
755        let non_nullable_dtype = DType::Decimal(decimal_dtype, Nullability::NonNullable);
756        let casted = array
757            .into_array()
758            .cast(non_nullable_dtype.clone())
759            .unwrap()
760            .execute::<DecimalArray>(&mut ctx)
761            .unwrap();
762
763        assert_eq!(casted.dtype(), &non_nullable_dtype);
764        assert!(matches!(casted.validity(), Ok(Validity::NonNullable)));
765    }
766
767    #[test]
768    #[should_panic(expected = "Cannot cast array with invalid values to non-nullable type")]
769    fn cast_nullable_with_nulls_to_non_nullable_fails() {
770        let mut ctx = array_session().create_execution_ctx();
771        let decimal_dtype = DecimalDType::new(10, 2);
772
773        // Create nullable array with nulls
774        let array = DecimalArray::from_option_iter([Some(100i32), None, Some(300)], decimal_dtype);
775
776        // Attempt to cast to non-nullable should fail
777        let non_nullable_dtype = DType::Decimal(decimal_dtype, Nullability::NonNullable);
778        array
779            .into_array()
780            .cast(non_nullable_dtype)
781            .and_then(|a| a.execute::<Canonical>(&mut ctx).map(|c| c.into_array()))
782            .unwrap();
783    }
784
785    #[test]
786    fn cast_different_scale_rescales() {
787        let mut ctx = array_session().create_execution_ctx();
788        let array = DecimalArray::new(
789            buffer![100i32],
790            DecimalDType::new(10, 2),
791            Validity::NonNullable,
792        );
793
794        // Cast 1.00 to scale 3, where it is stored as 1000.
795        let different_dtype = DType::Decimal(DecimalDType::new(15, 3), Nullability::NonNullable);
796        let casted = array
797            .into_array()
798            .cast(different_dtype)
799            .unwrap()
800            .execute::<DecimalArray>(&mut ctx)
801            .unwrap();
802
803        assert_eq!(casted.precision(), 15);
804        assert_eq!(casted.scale(), 3);
805        assert_eq!(casted.values_type(), DecimalType::I64);
806        assert_eq!(casted.buffer::<i64>().as_ref(), &[1000]);
807    }
808
809    #[test]
810    fn cast_downcast_precision_succeeds_when_values_fit() {
811        let mut ctx = array_session().create_execution_ctx();
812        let array = DecimalArray::new(
813            buffer![100i64],
814            DecimalDType::new(18, 2),
815            Validity::NonNullable,
816        );
817
818        // Downcasting precision is allowed when every value fits.
819        let smaller_dtype = DType::Decimal(DecimalDType::new(10, 2), Nullability::NonNullable);
820        let casted = array
821            .into_array()
822            .cast(smaller_dtype)
823            .unwrap()
824            .execute::<DecimalArray>(&mut ctx)
825            .unwrap();
826
827        assert_eq!(casted.precision(), 10);
828        assert_eq!(casted.scale(), 2);
829        assert_eq!(casted.buffer::<i64>().as_ref(), &[100]);
830    }
831
832    #[test]
833    fn cast_downcast_precision_checks_values() {
834        let mut ctx = array_session().create_execution_ctx();
835        let array = DecimalArray::new(
836            buffer![1000i64],
837            DecimalDType::new(18, 0),
838            Validity::NonNullable,
839        );
840
841        let smaller_dtype = DType::Decimal(DecimalDType::new(3, 0), Nullability::NonNullable);
842        let result = array
843            .into_array()
844            .cast(smaller_dtype)
845            .and_then(|a| a.execute::<Canonical>(&mut ctx).map(|c| c.into_array()));
846
847        assert!(result.is_err());
848        assert!(
849            result
850                .unwrap_err()
851                .to_string()
852                .contains("does not fit in precision")
853        );
854    }
855
856    #[test]
857    fn cast_lower_scale_requires_exact_rescale() {
858        let mut ctx = array_session().create_execution_ctx();
859        let array = DecimalArray::new(
860            buffer![123456i64],
861            DecimalDType::new(10, 4),
862            Validity::NonNullable,
863        );
864
865        let lower_scale_dtype = DType::Decimal(DecimalDType::new(10, 2), Nullability::NonNullable);
866        let result = array
867            .into_array()
868            .cast(lower_scale_dtype)
869            .and_then(|a| a.execute::<Canonical>(&mut ctx).map(|c| c.into_array()));
870
871        assert!(result.is_err());
872        assert!(
873            result
874                .unwrap_err()
875                .to_string()
876                .contains("would lose precision")
877        );
878    }
879
880    #[test]
881    fn cast_lower_scale_ignores_null_lane_failures() {
882        let mut ctx = array_session().create_execution_ctx();
883        let array = DecimalArray::new(
884            buffer![100i64, 123456],
885            DecimalDType::new(10, 4),
886            Validity::from_iter([true, false]),
887        );
888
889        let lower_scale_dtype = DType::Decimal(DecimalDType::new(3, 2), Nullability::Nullable);
890        let casted = array
891            .into_array()
892            .cast(lower_scale_dtype)
893            .unwrap()
894            .execute::<DecimalArray>(&mut ctx)
895            .unwrap();
896
897        let mask = casted
898            .as_ref()
899            .validity()
900            .unwrap()
901            .execute_mask(
902                casted.as_ref().len(),
903                &mut array_session().create_execution_ctx(),
904            )
905            .unwrap();
906        assert!(mask.value(0));
907        assert!(!mask.value(1));
908        assert_eq!(casted.buffer::<i16>().as_ref()[0], 1);
909    }
910
911    #[test]
912    fn cast_upcast_precision_succeeds() {
913        let mut ctx = array_session().create_execution_ctx();
914        let array = DecimalArray::new(
915            buffer![100i32, 200, 300],
916            DecimalDType::new(10, 2),
917            Validity::NonNullable,
918        );
919
920        // Cast to higher precision with same scale - should succeed
921        let wider_dtype = DType::Decimal(DecimalDType::new(38, 2), Nullability::NonNullable);
922        let casted = array
923            .into_array()
924            .cast(wider_dtype)
925            .unwrap()
926            .execute::<DecimalArray>(&mut ctx)
927            .unwrap();
928
929        assert_eq!(casted.precision(), 38);
930        assert_eq!(casted.scale(), 2);
931        assert_eq!(casted.len(), 3);
932        // Should be stored in i128 now (precision 38 requires i128)
933        assert_eq!(casted.values_type(), DecimalType::I128);
934    }
935
936    #[test]
937    fn cast_widening_same_physical_type_is_zero_copy() {
938        let mut ctx = array_session().create_execution_ctx();
939        // Decimal(10,2) and Decimal(18,2) are both physically i64 with the same scale, so widening
940        // the precision must reuse the values buffer rather than allocate and re-scan it.
941        let array = DecimalArray::new(
942            buffer![100i64, 200, 300],
943            DecimalDType::new(10, 2),
944            Validity::NonNullable,
945        );
946        let src_ptr = array.buffer::<i64>().as_ptr();
947
948        let wider_dtype = DType::Decimal(DecimalDType::new(18, 2), Nullability::NonNullable);
949        let casted = array
950            .into_array()
951            .cast(wider_dtype)
952            .unwrap()
953            .execute::<DecimalArray>(&mut ctx)
954            .unwrap();
955
956        assert_eq!(casted.precision(), 18);
957        assert_eq!(casted.scale(), 2);
958        assert_eq!(casted.values_type(), DecimalType::I64);
959        assert_eq!(casted.buffer::<i64>().as_ref(), &[100, 200, 300]);
960        // The values buffer must be shared with the source (zero-copy), not reallocated.
961        assert_eq!(
962            casted.buffer::<i64>().as_ptr(),
963            src_ptr,
964            "precision-widening cast must reuse the source values buffer"
965        );
966    }
967
968    #[test]
969    fn cast_to_non_decimal_returns_err() {
970        let mut ctx = array_session().create_execution_ctx();
971        let array = DecimalArray::new(
972            buffer![100i32],
973            DecimalDType::new(10, 2),
974            Validity::NonNullable,
975        );
976
977        // Try to cast to non-decimal type - should fail since no kernel can handle it
978        let result = array
979            .into_array()
980            .cast(DType::Utf8(Nullability::NonNullable))
981            .and_then(|a| a.execute::<Canonical>(&mut ctx).map(|c| c.into_array()));
982
983        assert!(result.is_err());
984        assert!(
985            result
986                .unwrap_err()
987                .to_string()
988                .contains("No CastKernel to cast canonical array")
989        );
990    }
991
992    #[rstest]
993    #[case(DecimalArray::new(buffer![100i32, 200, 300], DecimalDType::new(10, 2), Validity::NonNullable))]
994    #[case(DecimalArray::new(buffer![10000i64, 20000, 30000], DecimalDType::new(18, 4), Validity::NonNullable))]
995    #[case(DecimalArray::from_option_iter([Some(100i32), None, Some(300)], DecimalDType::new(10, 2)))]
996    #[case(DecimalArray::new(buffer![42i32], DecimalDType::new(5, 1), Validity::NonNullable))]
997    fn test_cast_decimal_conformance(#[case] array: DecimalArray) {
998        test_cast_conformance(
999            &array.into_array(),
1000            &mut array_session().create_execution_ctx(),
1001        );
1002    }
1003
1004    #[test]
1005    fn upcast_decimal_values_i32_to_i64() {
1006        let decimal_dtype = DecimalDType::new(10, 2);
1007        let array = DecimalArray::new(
1008            buffer![100i32, 200, 300],
1009            decimal_dtype,
1010            Validity::NonNullable,
1011        );
1012
1013        assert_eq!(array.values_type(), DecimalType::I32);
1014
1015        let array = array.as_view();
1016        let casted = upcast_decimal_values(array, DecimalType::I64).unwrap();
1017
1018        assert_eq!(casted.values_type(), DecimalType::I64);
1019        assert_eq!(casted.decimal_dtype(), decimal_dtype);
1020        assert_eq!(casted.len(), 3);
1021
1022        // Verify values are preserved
1023        let buffer = casted.buffer::<i64>();
1024        assert_eq!(buffer.as_ref(), &[100i64, 200, 300]);
1025    }
1026
1027    #[test]
1028    fn upcast_decimal_values_i64_to_i128() {
1029        let decimal_dtype = DecimalDType::new(18, 4);
1030        let array = DecimalArray::new(
1031            buffer![10000i64, 20000, 30000],
1032            decimal_dtype,
1033            Validity::NonNullable,
1034        );
1035
1036        let array = array.as_view();
1037        let casted = upcast_decimal_values(array, DecimalType::I128).unwrap();
1038
1039        assert_eq!(casted.values_type(), DecimalType::I128);
1040        assert_eq!(casted.decimal_dtype(), decimal_dtype);
1041
1042        let buffer = casted.buffer::<i128>();
1043        assert_eq!(buffer.as_ref(), &[10000i128, 20000, 30000]);
1044    }
1045
1046    #[test]
1047    fn upcast_decimal_values_same_type_returns_clone() {
1048        let decimal_dtype = DecimalDType::new(10, 2);
1049        let array = DecimalArray::new(
1050            buffer![100i32, 200, 300],
1051            decimal_dtype,
1052            Validity::NonNullable,
1053        );
1054
1055        let array = array.as_view();
1056        let casted = upcast_decimal_values(array, DecimalType::I32).unwrap();
1057
1058        assert_eq!(casted.values_type(), DecimalType::I32);
1059        assert_eq!(casted.decimal_dtype(), decimal_dtype);
1060    }
1061
1062    #[test]
1063    fn upcast_decimal_values_with_nulls() {
1064        let decimal_dtype = DecimalDType::new(10, 2);
1065        let array = DecimalArray::from_option_iter([Some(100i32), None, Some(300)], decimal_dtype);
1066
1067        let array = array.as_view();
1068        let casted = upcast_decimal_values(array, DecimalType::I64).unwrap();
1069
1070        assert_eq!(casted.values_type(), DecimalType::I64);
1071        assert_eq!(casted.len(), 3);
1072
1073        // Check validity is preserved
1074        let mask = casted
1075            .as_ref()
1076            .validity()
1077            .unwrap()
1078            .execute_mask(
1079                casted.as_ref().len(),
1080                &mut array_session().create_execution_ctx(),
1081            )
1082            .unwrap();
1083        assert!(mask.value(0));
1084        assert!(!mask.value(1));
1085        assert!(mask.value(2));
1086
1087        // Check non-null values
1088        let buffer = casted.buffer::<i64>();
1089        assert_eq!(buffer[0], 100);
1090        assert_eq!(buffer[2], 300);
1091    }
1092
1093    #[test]
1094    fn upcast_decimal_values_downcast_fails() {
1095        let decimal_dtype = DecimalDType::new(18, 4);
1096        let array = DecimalArray::new(
1097            buffer![10000i64, 20000, 30000],
1098            decimal_dtype,
1099            Validity::NonNullable,
1100        );
1101
1102        // Attempt to downcast from i64 to i32 should fail
1103        let array = array.as_view();
1104        let result = upcast_decimal_values(array, DecimalType::I32);
1105        assert!(result.is_err());
1106        assert!(
1107            result
1108                .unwrap_err()
1109                .to_string()
1110                .contains("Cannot downcast decimal values")
1111        );
1112    }
1113
1114    #[test]
1115    fn cast_decimal_f64() {
1116        let dtype = DecimalDType::new(6, 2);
1117        let array = DecimalArray::new(buffer![125i32, 250, -375], dtype, Validity::NonNullable);
1118        let target = DType::Primitive(PType::F64, Nullability::NonNullable);
1119        let casted = array.into_array().cast(target.clone()).unwrap();
1120        assert_eq!(casted.dtype(), &target);
1121
1122        let mut ctx = array_session().create_execution_ctx();
1123        let primitive = casted
1124            .execute::<Canonical>(&mut ctx)
1125            .unwrap()
1126            .into_primitive();
1127        assert_eq!(primitive.to_buffer::<f64>().as_ref(), &[1.25, 2.5, -3.75]);
1128    }
1129
1130    #[test]
1131    fn cast_decimal_f64_null() {
1132        let values = [Some(100i32), None, Some(-200)];
1133        let array = DecimalArray::from_option_iter(values, DecimalDType::new(6, 2));
1134        let target = DType::Primitive(PType::F64, Nullability::Nullable);
1135        let casted = array.into_array().cast(target.clone()).unwrap();
1136        assert_eq!(casted.dtype(), &target);
1137
1138        let mut ctx = array_session().create_execution_ctx();
1139        let primitive = casted
1140            .execute::<Canonical>(&mut ctx)
1141            .unwrap()
1142            .into_primitive();
1143
1144        assert!(primitive.is_valid(0, &mut ctx).unwrap());
1145        assert!(!primitive.is_valid(1, &mut ctx).unwrap());
1146        assert!(primitive.is_valid(2, &mut ctx).unwrap());
1147
1148        assert_eq!(
1149            primitive.execute_scalar(0, &mut ctx).unwrap(),
1150            Scalar::from(1f64)
1151        );
1152        assert_eq!(
1153            primitive.execute_scalar(2, &mut ctx).unwrap(),
1154            Scalar::from(-2f64)
1155        );
1156    }
1157
1158    #[test]
1159    fn cast_decimal_f64_all_null() {
1160        let dtype = DecimalDType::new(6, 2);
1161        let buf = buffer![i32::MAX, i32::MIN, 12345];
1162        let array = DecimalArray::new(buf, dtype, Validity::AllInvalid);
1163        let target = DType::Primitive(PType::F64, Nullability::Nullable);
1164        let casted = array.into_array().cast(target.clone()).unwrap();
1165        assert_eq!(casted.dtype(), &target);
1166
1167        let primitive = casted
1168            .execute::<Canonical>(&mut array_session().create_execution_ctx())
1169            .unwrap()
1170            .into_primitive();
1171        let mut ctx = array_session().create_execution_ctx();
1172        for i in 0..2 {
1173            assert!(!primitive.is_valid(i, &mut ctx).unwrap());
1174        }
1175        assert_eq!(primitive.to_buffer::<f64>().as_ref(), &[0.0, 0.0, 0.0]);
1176    }
1177}