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