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::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 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 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 array.dtype() == dtype {
113 return Ok(Some(array.array().clone()));
114 }
115
116 let validity = array.validity()?;
117
118 let new_validity = validity
120 .clone()
121 .cast_nullability(*to_nullability, array.len(), ctx)?;
122
123 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 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 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 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 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 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
428pub 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 from_values_type == to_values_type {
447 return Ok(array.array().as_::<Decimal>().into_owned());
448 }
449
450 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 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
472fn 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 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 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 let array = DecimalArray::new(buffer![100i32, 200, 300], decimal_dtype, Validity::AllValid);
753
754 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 let array = DecimalArray::from_option_iter([Some(100i32), None, Some(300)], decimal_dtype);
775
776 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 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 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 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 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 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 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 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 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 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 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 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}