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