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