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