Skip to main content

vortex_decimal_byte_parts/decimal_byte_parts/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::fmt::Display;
5use std::fmt::Formatter;
6use std::hash::Hasher;
7
8use vortex_array::Array;
9use vortex_array::ArrayParts;
10use vortex_array::ArrayView;
11pub(crate) mod compute;
12mod rules;
13mod slice;
14
15use prost::Message as _;
16use vortex_array::ArrayEq;
17use vortex_array::ArrayHash;
18use vortex_array::ArrayId;
19use vortex_array::ArrayRef;
20use vortex_array::EqMode;
21use vortex_array::ExecutionCtx;
22use vortex_array::ExecutionResult;
23use vortex_array::IntoArray;
24use vortex_array::array_slots;
25use vortex_array::arrays::DecimalArray;
26use vortex_array::arrays::PrimitiveArray;
27use vortex_array::buffer::BufferHandle;
28use vortex_array::dtype::DType;
29use vortex_array::dtype::DecimalDType;
30use vortex_array::dtype::PType;
31use vortex_array::match_each_signed_integer_ptype;
32use vortex_array::scalar::DecimalValue;
33use vortex_array::scalar::Scalar;
34use vortex_array::scalar::ScalarValue;
35use vortex_array::serde::ArrayChildren;
36use vortex_array::smallvec::smallvec;
37use vortex_array::vtable::OperationsVTable;
38use vortex_array::vtable::VTable;
39use vortex_array::vtable::ValidityChild;
40use vortex_array::vtable::ValidityVTableFromChild;
41use vortex_error::VortexExpect;
42use vortex_error::VortexResult;
43use vortex_error::vortex_bail;
44use vortex_error::vortex_ensure;
45use vortex_error::vortex_panic;
46use vortex_session::VortexSession;
47use vortex_session::registry::CachedId;
48
49use crate::decimal_byte_parts::rules::PARENT_RULES;
50
51/// A [`DecimalByteParts`]-encoded Vortex array.
52pub type DecimalBytePartsArray = Array<DecimalByteParts>;
53
54impl ArrayHash for DecimalBytePartsData {
55    fn array_hash<H: Hasher>(&self, _state: &mut H, _accuracy: EqMode) {}
56}
57
58impl ArrayEq for DecimalBytePartsData {
59    fn array_eq(&self, _other: &Self, _accuracy: EqMode) -> bool {
60        true
61    }
62}
63
64#[derive(Clone, prost::Message)]
65pub struct DecimalBytesPartsMetadata {
66    #[prost(enumeration = "PType", tag = "1")]
67    zeroth_child_ptype: i32,
68    #[prost(uint32, tag = "2")]
69    lower_part_count: u32,
70}
71
72impl VTable for DecimalByteParts {
73    type TypedArrayData = DecimalBytePartsData;
74
75    type OperationsVTable = Self;
76    type ValidityVTable = ValidityVTableFromChild;
77
78    fn id(&self) -> ArrayId {
79        static ID: CachedId = CachedId::new("vortex.decimal_byte_parts");
80        *ID
81    }
82
83    fn validate(
84        &self,
85        _data: &Self::TypedArrayData,
86        dtype: &DType,
87        len: usize,
88        slots: &[Option<ArrayRef>],
89    ) -> VortexResult<()> {
90        let Some(decimal_dtype) = dtype.as_decimal_opt() else {
91            vortex_bail!("expected decimal dtype, got {}", dtype)
92        };
93        let msp = DecimalBytePartsSlotsView::from_slots(slots).msp;
94        DecimalBytePartsData::validate(msp, *decimal_dtype, dtype, len)
95    }
96
97    fn nbuffers(_array: ArrayView<'_, Self>) -> usize {
98        0
99    }
100
101    fn buffer(_array: ArrayView<'_, Self>, idx: usize) -> BufferHandle {
102        vortex_panic!("DecimalBytePartsArray buffer index {idx} out of bounds")
103    }
104
105    fn buffer_name(_array: ArrayView<'_, Self>, idx: usize) -> Option<String> {
106        vortex_panic!("DecimalBytePartsArray buffer_name index {idx} out of bounds")
107    }
108
109    fn with_buffers(
110        &self,
111        array: ArrayView<'_, Self>,
112        buffers: &[BufferHandle],
113    ) -> VortexResult<ArrayParts<Self>> {
114        vortex_array::vtable::with_empty_buffers(self, array, buffers)
115    }
116
117    fn serialize(
118        array: ArrayView<'_, Self>,
119        _session: &VortexSession,
120    ) -> VortexResult<Option<Vec<u8>>> {
121        Ok(Some(
122            DecimalBytesPartsMetadata {
123                zeroth_child_ptype: PType::try_from(array.msp().dtype())? as i32,
124                lower_part_count: 0,
125            }
126            .encode_to_vec(),
127        ))
128    }
129
130    fn deserialize(
131        &self,
132        dtype: &DType,
133        len: usize,
134        metadata: &[u8],
135        _buffers: &[BufferHandle],
136        children: &dyn ArrayChildren,
137        _session: &VortexSession,
138    ) -> VortexResult<ArrayParts<Self>> {
139        let metadata = DecimalBytesPartsMetadata::decode(metadata)?;
140        let Some(decimal_dtype) = dtype.as_decimal_opt() else {
141            vortex_bail!("decoding decimal but given non decimal dtype {}", dtype)
142        };
143
144        let encoded_dtype = DType::Primitive(metadata.zeroth_child_ptype(), dtype.nullability());
145
146        let msp = children.get(0, &encoded_dtype, len)?;
147
148        assert_eq!(
149            metadata.lower_part_count, 0,
150            "lower_part_count > 0 not currently supported"
151        );
152
153        let slots = smallvec![Some(msp.clone())];
154        let data = DecimalBytePartsData::try_new(msp.dtype(), msp.len(), *decimal_dtype)?;
155        Ok(ArrayParts::new(self.clone(), dtype.clone(), len, data).with_slots(slots))
156    }
157
158    fn slot_name(_array: ArrayView<'_, Self>, idx: usize) -> String {
159        DecimalBytePartsSlots::NAMES[idx].to_string()
160    }
161
162    fn reduce_parent(
163        array: ArrayView<'_, Self>,
164        parent: &ArrayRef,
165        child_idx: usize,
166    ) -> VortexResult<Option<ArrayRef>> {
167        PARENT_RULES.evaluate(array, parent, child_idx)
168    }
169
170    fn execute(array: Array<Self>, ctx: &mut ExecutionCtx) -> VortexResult<ExecutionResult> {
171        to_canonical_decimal(&array, ctx).map(ExecutionResult::done)
172    }
173}
174
175#[array_slots(DecimalByteParts)]
176pub struct DecimalBytePartsSlots {
177    /// The most significant parts of the decimal values.
178    pub msp: ArrayRef,
179}
180
181/// This array encodes decimals as between 1-4 columns of primitive typed children.
182/// The most significant part (msp) sorting the most significant decimal bits.
183/// This array must be signed and is nullable iff the decimal is nullable.
184///
185/// e.g. for a decimal i128 \[ 127..64 | 64..0 \] msp = 127..64 and lower_part\[0\] = 64..0
186#[derive(Clone, Debug)]
187pub struct DecimalBytePartsData {
188    // NOTE: the lower_parts is currently unused, we reserve this field so that it is properly
189    //  read/written during serde, but provide no constructor to initialize this to anything
190    //  other than the empty Vec.
191    _lower_parts: Vec<ArrayRef>,
192}
193
194impl Display for DecimalBytePartsData {
195    fn fmt(&self, _f: &mut Formatter<'_>) -> std::fmt::Result {
196        Ok(())
197    }
198}
199
200pub struct DecimalBytePartsDataParts {
201    pub msp: ArrayRef,
202}
203
204impl DecimalBytePartsData {
205    pub fn validate(
206        msp: &ArrayRef,
207        decimal_dtype: DecimalDType,
208        dtype: &DType,
209        len: usize,
210    ) -> VortexResult<()> {
211        if !msp.dtype().is_signed_int() {
212            vortex_bail!("decimal bytes parts, first part must be a signed array")
213        }
214
215        let expected_dtype = DType::Decimal(decimal_dtype, msp.dtype().nullability());
216        vortex_ensure!(
217            dtype == &expected_dtype,
218            "expected dtype {expected_dtype}, got {dtype}"
219        );
220        vortex_ensure!(msp.len() == len, "expected len {len}, got {}", msp.len());
221        Ok(())
222    }
223
224    pub(crate) fn try_new(
225        msp_dtype: &DType,
226        msp_len: usize,
227        decimal_dtype: DecimalDType,
228    ) -> VortexResult<Self> {
229        let expected_dtype = DType::Decimal(decimal_dtype, msp_dtype.nullability());
230        vortex_ensure!(
231            msp_dtype.is_signed_int(),
232            "decimal bytes parts, first part must be a signed array"
233        );
234        let _ = msp_len;
235        drop(expected_dtype);
236        Ok(Self {
237            _lower_parts: Vec::new(),
238        })
239    }
240}
241
242#[derive(Clone, Debug)]
243pub struct DecimalByteParts;
244
245impl DecimalByteParts {
246    /// Construct a new [`DecimalBytePartsArray`] from an MSP array and decimal dtype.
247    pub fn try_new(
248        msp: ArrayRef,
249        decimal_dtype: DecimalDType,
250    ) -> VortexResult<DecimalBytePartsArray> {
251        let len = msp.len();
252        let dtype = DType::Decimal(decimal_dtype, msp.dtype().nullability());
253        let slots = smallvec![Some(msp.clone())];
254        let data = DecimalBytePartsData::try_new(msp.dtype(), msp.len(), decimal_dtype)?;
255        Ok(unsafe {
256            Array::from_parts_unchecked(
257                ArrayParts::new(DecimalByteParts, dtype, len, data).with_slots(slots),
258            )
259        })
260    }
261}
262
263/// Converts a DecimalBytePartsArray to its canonical DecimalArray representation.
264fn to_canonical_decimal(
265    array: &DecimalBytePartsArray,
266    ctx: &mut ExecutionCtx,
267) -> VortexResult<ArrayRef> {
268    // TODO(joe): support parts len != 1
269    let prim = array.msp().clone().execute::<PrimitiveArray>(ctx)?;
270    // Depending on the decimal type and the min/max of the primitive array we can choose
271    // the correct buffer size
272
273    Ok(match_each_signed_integer_ptype!(prim.ptype(), |P| {
274        // SAFETY: The primitive array's buffer is already validated with correct type.
275        // The decimal dtype matches the array's dtype, and validity is preserved.
276        unsafe {
277            DecimalArray::new_unchecked(
278                prim.to_buffer::<P>(),
279                *array
280                    .dtype()
281                    .as_decimal_opt()
282                    .vortex_expect("must be a decimal dtype"),
283                prim.validity()?,
284            )
285        }
286        .into_array()
287    }))
288}
289
290impl OperationsVTable<DecimalByteParts> for DecimalByteParts {
291    fn scalar_at(
292        array: ArrayView<'_, DecimalByteParts>,
293        index: usize,
294        ctx: &mut ExecutionCtx,
295    ) -> VortexResult<Scalar> {
296        // TODO(joe): support parts len != 1
297        let scalar = array.msp().execute_scalar(index, ctx)?;
298
299        // Note. values in msp, can only be signed integers upto size i64.
300        let primitive_scalar = scalar.as_primitive();
301        // TODO(joe): extend this to support multiple parts.
302        let value = primitive_scalar.as_::<i64>().vortex_expect("non-null");
303        Scalar::try_new(
304            array.dtype().clone(),
305            Some(ScalarValue::Decimal(DecimalValue::I64(value))),
306        )
307    }
308}
309
310impl ValidityChild<DecimalByteParts> for DecimalByteParts {
311    fn validity_child(array: ArrayView<'_, DecimalByteParts>) -> ArrayRef {
312        // validity stored in 0th child
313        array.msp().clone()
314    }
315}
316
317#[cfg(test)]
318mod tests {
319    use vortex_array::IntoArray;
320    use vortex_array::VortexSessionExecute;
321    use vortex_array::array_session;
322    use vortex_array::arrays::BoolArray;
323    use vortex_array::arrays::PrimitiveArray;
324    use vortex_array::dtype::DType;
325    use vortex_array::dtype::DecimalDType;
326    use vortex_array::dtype::Nullability;
327    use vortex_array::scalar::DecimalValue;
328    use vortex_array::scalar::Scalar;
329    use vortex_array::scalar::ScalarValue;
330    use vortex_array::validity::Validity;
331    use vortex_buffer::buffer;
332
333    use crate::DecimalByteParts;
334
335    #[test]
336    fn test_scalar_at_decimal_parts() {
337        let decimal_dtype = DecimalDType::new(8, 2);
338        let dtype = DType::Decimal(decimal_dtype, Nullability::Nullable);
339        let array = DecimalByteParts::try_new(
340            PrimitiveArray::new(
341                buffer![100i32, 200i32, 400i32],
342                Validity::Array(BoolArray::from_iter(vec![false, true, true]).into_array()),
343            )
344            .into_array(),
345            decimal_dtype,
346        )
347        .unwrap()
348        .into_array();
349
350        assert_eq!(
351            Scalar::null(dtype.clone()),
352            array
353                .execute_scalar(0, &mut array_session().create_execution_ctx())
354                .unwrap()
355        );
356        assert_eq!(
357            Scalar::try_new(
358                dtype.clone(),
359                Some(ScalarValue::Decimal(DecimalValue::I64(200)))
360            )
361            .unwrap(),
362            array
363                .execute_scalar(1, &mut array_session().create_execution_ctx())
364                .unwrap()
365        );
366        assert_eq!(
367            Scalar::try_new(dtype, Some(ScalarValue::Decimal(DecimalValue::I64(400)))).unwrap(),
368            array
369                .execute_scalar(2, &mut array_session().create_execution_ctx())
370                .unwrap()
371        );
372    }
373}