vortex_fastlanes/delta/compute/
cast.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use vortex_array::ArrayRef;
5use vortex_array::IntoArray;
6use vortex_array::compute::CastKernel;
7use vortex_array::compute::CastKernelAdapter;
8use vortex_array::compute::cast;
9use vortex_array::register_kernel;
10use vortex_dtype::DType;
11use vortex_dtype::Nullability::NonNullable;
12use vortex_error::VortexResult;
13use vortex_error::vortex_panic;
14
15use crate::delta::DeltaArray;
16use crate::delta::DeltaVTable;
17
18impl CastKernel for DeltaVTable {
19    fn cast(&self, array: &DeltaArray, dtype: &DType) -> VortexResult<Option<ArrayRef>> {
20        // Delta encoding stores differences between consecutive values, which requires
21        // unsigned integers to avoid overflow issues. Signed integers could produce
22        // negative deltas that wouldn't fit in the unsigned delta representation.
23        // This encoding is optimized for monotonically increasing sequences.
24        let DType::Primitive(target_ptype, _) = dtype else {
25            return Ok(None);
26        };
27
28        let DType::Primitive(source_ptype, _) = array.dtype() else {
29            vortex_panic!("delta should be primitive typed");
30        };
31
32        // TODO(DK): narrows can be safe but we must decompress to compute the maximum value.
33        if target_ptype.is_signed_int() || source_ptype.bit_width() > target_ptype.bit_width() {
34            return Ok(None);
35        }
36
37        // Cast both bases and deltas to the target type
38        let casted_bases = cast(array.bases(), &dtype.with_nullability(NonNullable))?;
39        let casted_deltas = cast(array.deltas(), dtype)?;
40
41        // Create a new DeltaArray with the casted components
42        Ok(Some(
43            DeltaArray::try_from_delta_compress_parts(casted_bases, casted_deltas)?.into_array(),
44        ))
45    }
46}
47
48register_kernel!(CastKernelAdapter(DeltaVTable).lift());
49
50#[cfg(test)]
51mod tests {
52    use rstest::rstest;
53    use vortex_array::ToCanonical;
54    use vortex_array::arrays::PrimitiveArray;
55    use vortex_array::assert_arrays_eq;
56    use vortex_array::compute::cast;
57    use vortex_array::compute::conformance::cast::test_cast_conformance;
58    use vortex_buffer::Buffer;
59    use vortex_dtype::DType;
60    use vortex_dtype::Nullability;
61    use vortex_dtype::PType;
62
63    use crate::delta::DeltaArray;
64
65    #[test]
66    fn test_cast_delta_u8_to_u32() {
67        let primitive = PrimitiveArray::new(
68            Buffer::copy_from(vec![10u8, 20, 30, 40, 50]),
69            vortex_array::validity::Validity::NonNullable,
70        );
71        let array = DeltaArray::try_from_primitive_array(&primitive).unwrap();
72
73        let casted = cast(
74            array.as_ref(),
75            &DType::Primitive(PType::U32, Nullability::NonNullable),
76        )
77        .unwrap();
78        assert_eq!(
79            casted.dtype(),
80            &DType::Primitive(PType::U32, Nullability::NonNullable)
81        );
82
83        // Verify by decoding
84        let decoded = casted.to_primitive();
85        assert_arrays_eq!(decoded, PrimitiveArray::from_iter([10u32, 20, 30, 40, 50]));
86    }
87
88    #[test]
89    fn test_cast_delta_nullable() {
90        // DeltaArray doesn't support nullable arrays - the validity is handled at the DeltaArray level
91        // Create a non-nullable array and then add validity to the DeltaArray
92        let values = PrimitiveArray::new(
93            Buffer::copy_from(vec![100u16, 0, 200, 300, 0]),
94            vortex_array::validity::Validity::NonNullable,
95        );
96        let array = DeltaArray::try_from_primitive_array(&values).unwrap();
97
98        let casted = cast(
99            array.as_ref(),
100            &DType::Primitive(PType::U32, Nullability::Nullable),
101        )
102        .unwrap();
103        assert_eq!(
104            casted.dtype(),
105            &DType::Primitive(PType::U32, Nullability::Nullable)
106        );
107    }
108
109    #[rstest]
110    #[case::u8(
111        PrimitiveArray::new(
112            Buffer::copy_from(vec![0u8, 10, 20, 30, 40, 50]),
113            vortex_array::validity::Validity::NonNullable,
114        )
115    )]
116    #[case::u16(
117        PrimitiveArray::new(
118            Buffer::copy_from(vec![0u16, 100, 200, 300, 400, 500]),
119            vortex_array::validity::Validity::NonNullable,
120        )
121    )]
122    #[case::u32(
123        PrimitiveArray::new(
124            Buffer::copy_from(vec![0u32, 1000, 2000, 3000, 4000]),
125            vortex_array::validity::Validity::NonNullable,
126        )
127    )]
128    #[case::u64(
129        PrimitiveArray::new(
130            Buffer::copy_from(vec![0u64, 10000, 20000, 30000]),
131            vortex_array::validity::Validity::NonNullable,
132        )
133    )]
134    fn test_cast_delta_conformance(#[case] primitive: PrimitiveArray) {
135        let delta_array = DeltaArray::try_from_primitive_array(&primitive).unwrap();
136        test_cast_conformance(delta_array.as_ref());
137    }
138}