Skip to main content

vortex_array/builders/
decimal.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::any::Any;
5
6use vortex_buffer::BufferMut;
7use vortex_error::VortexExpect;
8use vortex_error::VortexResult;
9use vortex_error::vortex_ensure;
10use vortex_error::vortex_err;
11use vortex_error::vortex_panic;
12use vortex_mask::Mask;
13
14use crate::ArrayRef;
15use crate::ExecutionCtx;
16use crate::IntoArray;
17use crate::arrays::DecimalArray;
18use crate::builders::ArrayBuilder;
19use crate::builders::DEFAULT_BUILDER_CAPACITY;
20use crate::builders::LazyBitBufferBuilder;
21use crate::canonical::Canonical;
22use crate::dtype::BigCast;
23use crate::dtype::DType;
24use crate::dtype::DecimalDType;
25use crate::dtype::NativeDecimalType;
26use crate::dtype::Nullability;
27use crate::dtype::i256;
28use crate::match_each_decimal_value;
29use crate::match_each_decimal_value_type;
30use crate::scalar::DecimalValue;
31use crate::scalar::Scalar;
32
33/// The builder for building a [`DecimalArray`].
34///
35/// The output will be a new [`DecimalArray`] holding values of `T`. Any value that is a valid
36/// [decimal type][NativeDecimalType] can be appended to the builder and it will be immediately
37/// coerced into the target type.
38pub struct DecimalBuilder {
39    dtype: DType,
40    values: DecimalBuffer,
41    nulls: LazyBitBufferBuilder,
42}
43
44/// Wrapper around the typed builder.
45///
46/// We want to be able to downcast a `Box<dyn ArrayBuilder>` to a [`DecimalBuilder`] and we
47/// generally don't have enough type information to get the `T` at the call site, so we instead use
48/// this to hold values and can push values into the correct buffer type generically.
49enum DecimalBuffer {
50    I8(BufferMut<i8>),
51    I16(BufferMut<i16>),
52    I32(BufferMut<i32>),
53    I64(BufferMut<i64>),
54    I128(BufferMut<i128>),
55    I256(BufferMut<i256>),
56}
57
58macro_rules! delegate_fn {
59    ($self:expr, | $tname:ident, $buffer:ident | $body:block) => {{
60        #[allow(unused)]
61        match $self {
62            DecimalBuffer::I8(buffer) => {
63                type $tname = i8;
64                let $buffer = buffer;
65                $body
66            }
67            DecimalBuffer::I16(buffer) => {
68                type $tname = i16;
69                let $buffer = buffer;
70                $body
71            }
72            DecimalBuffer::I32(buffer) => {
73                type $tname = i32;
74                let $buffer = buffer;
75                $body
76            }
77            DecimalBuffer::I64(buffer) => {
78                type $tname = i64;
79                let $buffer = buffer;
80                $body
81            }
82            DecimalBuffer::I128(buffer) => {
83                type $tname = i128;
84                let $buffer = buffer;
85                $body
86            }
87            DecimalBuffer::I256(buffer) => {
88                type $tname = i256;
89                let $buffer = buffer;
90                $body
91            }
92        }
93    }};
94}
95
96impl DecimalBuilder {
97    /// Creates a new `DecimalBuilder` with a capacity of [`DEFAULT_BUILDER_CAPACITY`].
98    pub fn new<T: NativeDecimalType>(decimal: DecimalDType, nullability: Nullability) -> Self {
99        Self::with_capacity::<T>(DEFAULT_BUILDER_CAPACITY, decimal, nullability)
100    }
101
102    /// Creates a new `DecimalBuilder` with the given `capacity`.
103    pub fn with_capacity<T: NativeDecimalType>(
104        capacity: usize,
105        decimal: DecimalDType,
106        nullability: Nullability,
107    ) -> Self {
108        Self {
109            dtype: DType::Decimal(decimal, nullability),
110            values: match_each_decimal_value_type!(T::DECIMAL_TYPE, |D| {
111                DecimalBuffer::from(BufferMut::<D>::with_capacity(capacity))
112            }),
113            nulls: LazyBitBufferBuilder::new(capacity),
114        }
115    }
116
117    /// Appends a decimal `value` to the builder.
118    pub fn append_value<V: NativeDecimalType>(&mut self, value: V) {
119        self.values.push(value);
120        self.nulls.append_non_null();
121    }
122
123    /// Appends `n` copies of `value` as non-null entries, directly writing into the buffer.
124    pub fn append_n_values<V: NativeDecimalType>(&mut self, value: V, n: usize) {
125        self.values.push_n(value, n);
126        self.nulls.append_n_non_nulls(n);
127    }
128
129    /// Appends the values of a canonical [`DecimalArray`] to the builder, coercing the physical
130    /// storage type to the builder's type as needed.
131    pub(crate) fn append_decimal_array(
132        &mut self,
133        array: &DecimalArray,
134        ctx: &mut ExecutionCtx,
135    ) -> VortexResult<()> {
136        match_each_decimal_value_type!(array.values_type(), |D| {
137            // Extends the values buffer from another buffer of type D where D can be coerced to the
138            // builder type.
139            self.values.extend(array.buffer::<D>().iter().copied());
140        });
141
142        self.nulls.append_validity_mask(
143            &array
144                .as_ref()
145                .validity()?
146                .execute_mask(array.as_ref().len(), ctx)?,
147        );
148        Ok(())
149    }
150
151    /// Finishes the builder directly into a [`DecimalArray`].
152    pub fn finish_into_decimal(&mut self) -> DecimalArray {
153        let validity = self.nulls.finish_with_nullability(self.dtype.nullability());
154
155        let decimal_dtype = *self.decimal_dtype();
156
157        delegate_fn!(std::mem::take(&mut self.values), |T, values| {
158            DecimalArray::new::<T>(values.freeze(), decimal_dtype, validity)
159        })
160    }
161
162    /// The [`DecimalDType`] of this builder.
163    pub fn decimal_dtype(&self) -> &DecimalDType {
164        let DType::Decimal(decimal_dtype, _) = &self.dtype else {
165            vortex_panic!("`DecimalBuilder` somehow had dtype {}", self.dtype);
166        };
167
168        decimal_dtype
169    }
170}
171
172impl ArrayBuilder for DecimalBuilder {
173    fn as_any(&self) -> &dyn Any {
174        self
175    }
176
177    fn as_any_mut(&mut self) -> &mut dyn Any {
178        self
179    }
180
181    fn dtype(&self) -> &DType {
182        &self.dtype
183    }
184
185    fn len(&self) -> usize {
186        self.values.len()
187    }
188
189    fn append_zeros(&mut self, n: usize) {
190        self.values.push_n(0, n);
191        self.nulls.append_n_non_nulls(n);
192    }
193
194    unsafe fn append_nulls_unchecked(&mut self, n: usize) {
195        self.values.push_n(0, n);
196        self.nulls.append_n_nulls(n);
197    }
198
199    fn append_scalar(&mut self, scalar: &Scalar) -> VortexResult<()> {
200        vortex_ensure!(
201            scalar.dtype() == self.dtype(),
202            "DecimalBuilder expected scalar with dtype {}, got {}",
203            self.dtype(),
204            scalar.dtype()
205        );
206
207        match scalar.as_decimal().decimal_value() {
208            None => self.append_null(),
209            Some(v) => match_each_decimal_value!(v, |dec_val| {
210                self.append_value(dec_val);
211            }),
212        }
213
214        Ok(())
215    }
216
217    fn reserve_exact(&mut self, additional: usize) {
218        self.values.reserve(additional);
219        self.nulls.reserve_exact(additional);
220    }
221
222    unsafe fn set_validity_unchecked(&mut self, validity: Mask) {
223        self.nulls = LazyBitBufferBuilder::from_validity_mask(validity);
224    }
225
226    fn finish(&mut self) -> ArrayRef {
227        self.finish_into_decimal().into_array()
228    }
229
230    fn finish_into_canonical(&mut self, _ctx: &mut ExecutionCtx) -> Canonical {
231        Canonical::Decimal(self.finish_into_decimal())
232    }
233}
234
235impl DecimalBuffer {
236    fn push<V: NativeDecimalType>(&mut self, value: V) {
237        delegate_fn!(self, |T, buffer| {
238            buffer.push(
239                <T as BigCast>::from(value)
240                    .ok_or_else(|| {
241                        vortex_err!(
242                            "decimal conversion failure {:?}, type: {:?} to {:?}",
243                            value,
244                            V::DECIMAL_TYPE,
245                            T::DECIMAL_TYPE,
246                        )
247                    })
248                    .vortex_expect("operation should succeed in builder"),
249            )
250        });
251    }
252
253    fn push_n<V: NativeDecimalType>(&mut self, value: V, n: usize) {
254        delegate_fn!(self, |T, buffer| {
255            buffer.push_n(
256                <T as BigCast>::from(value).vortex_expect("decimal conversion failure"),
257                n,
258            )
259        });
260    }
261
262    fn reserve(&mut self, additional: usize) {
263        delegate_fn!(self, |T, buffer| { buffer.reserve(additional) })
264    }
265
266    fn len(&self) -> usize {
267        delegate_fn!(self, |T, buffer| { buffer.len() })
268    }
269
270    pub fn extend<I, V: NativeDecimalType>(&mut self, iter: I)
271    where
272        I: Iterator<Item = V>,
273    {
274        delegate_fn!(self, |T, buffer| {
275            buffer.extend(
276                iter.map(|x| <T as BigCast>::from(x).vortex_expect("decimal conversion failure")),
277            )
278        })
279    }
280}
281
282macro_rules! impl_from_buffer {
283    ($T:ty, $variant:ident) => {
284        impl From<BufferMut<$T>> for DecimalBuffer {
285            fn from(buffer: BufferMut<$T>) -> Self {
286                Self::$variant(buffer)
287            }
288        }
289    };
290}
291
292impl_from_buffer!(i8, I8);
293impl_from_buffer!(i16, I16);
294impl_from_buffer!(i32, I32);
295impl_from_buffer!(i64, I64);
296impl_from_buffer!(i128, I128);
297impl_from_buffer!(i256, I256);
298
299impl Default for DecimalBuffer {
300    fn default() -> Self {
301        Self::I8(BufferMut::<i8>::empty())
302    }
303}
304
305#[cfg(test)]
306mod tests {
307    use crate::VortexSessionExecute;
308    use crate::array_session;
309    use crate::assert_arrays_eq;
310    use crate::builders::ArrayBuilder;
311    use crate::builders::DecimalBuilder;
312    use crate::builders::decimal::DecimalArray;
313    use crate::dtype::DecimalDType;
314
315    #[test]
316    fn test_mixed_extend() {
317        let values = 42i8;
318
319        let mut i8s = DecimalBuilder::new::<i8>(DecimalDType::new(2, 1), false.into());
320        for v in 0..values {
321            i8s.append_value(v);
322        }
323        let i8s = i8s.finish();
324
325        let mut i128s = DecimalBuilder::new::<i128>(DecimalDType::new(2, 1), false.into());
326        i8s.append_to_builder(&mut i128s, &mut array_session().create_execution_ctx())
327            .unwrap();
328        let i128s = i128s.finish();
329
330        for i in 0..i8s.len() {
331            assert_eq!(
332                i8s.execute_scalar(i, &mut array_session().create_execution_ctx())
333                    .unwrap(),
334                i128s
335                    .execute_scalar(i, &mut array_session().create_execution_ctx())
336                    .unwrap()
337            );
338        }
339    }
340
341    #[test]
342    fn test_append_scalar() {
343        let mut ctx = array_session().create_execution_ctx();
344        use crate::scalar::Scalar;
345
346        // Simply test that the builder accepts its own finish output via scalar.
347        let mut builder = DecimalBuilder::new::<i64>(DecimalDType::new(10, 2), true.into());
348        builder.append_value(1234i64);
349        builder.append_value(5678i64);
350        builder.append_null();
351
352        let array = builder.finish();
353        let expected = DecimalArray::from_option_iter(
354            [Some(1234i64), Some(5678), None],
355            DecimalDType::new(10, 2),
356        );
357        assert_arrays_eq!(&array, &expected, &mut ctx);
358
359        // Test by taking a scalar from the array and appending it to a new builder.
360        let mut builder2 = DecimalBuilder::new::<i64>(DecimalDType::new(10, 2), true.into());
361        for i in 0..array.len() {
362            let scalar = array
363                .execute_scalar(i, &mut array_session().create_execution_ctx())
364                .unwrap();
365            builder2.append_scalar(&scalar).unwrap();
366        }
367
368        let array2 = builder2.finish();
369        assert_arrays_eq!(&array2, &array, &mut ctx);
370
371        // Test wrong dtype error.
372        let mut builder = DecimalBuilder::new::<i64>(DecimalDType::new(10, 2), false.into());
373        let wrong_scalar = Scalar::from(true);
374        assert!(builder.append_scalar(&wrong_scalar).is_err());
375    }
376}