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::BufferAllocatorRef;
7use vortex_buffer::BufferMut;
8use vortex_error::VortexExpect;
9use vortex_error::VortexResult;
10use vortex_error::vortex_ensure;
11use vortex_error::vortex_err;
12use vortex_error::vortex_panic;
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    #[deprecated(note = "use `new_in` with an explicit allocator")]
99    pub fn new<T: NativeDecimalType>(decimal: DecimalDType, nullability: Nullability) -> Self {
100        Self::new_in::<T>(decimal, nullability, BufferAllocatorRef::static_ref())
101    }
102
103    /// Creates a new `DecimalBuilder` with the default capacity using `allocator`.
104    pub fn new_in<T: NativeDecimalType>(
105        decimal: DecimalDType,
106        nullability: Nullability,
107        allocator: &BufferAllocatorRef,
108    ) -> Self {
109        Self::with_capacity_in::<T>(DEFAULT_BUILDER_CAPACITY, decimal, nullability, allocator)
110    }
111
112    /// Creates a new `DecimalBuilder` with the given `capacity`.
113    #[deprecated(note = "use `with_capacity_in` with an explicit allocator")]
114    pub fn with_capacity<T: NativeDecimalType>(
115        capacity: usize,
116        decimal: DecimalDType,
117        nullability: Nullability,
118    ) -> Self {
119        Self::with_capacity_in::<T>(
120            capacity,
121            decimal,
122            nullability,
123            BufferAllocatorRef::static_ref(),
124        )
125    }
126
127    /// Creates a new `DecimalBuilder` with `capacity` using `allocator`.
128    pub fn with_capacity_in<T: NativeDecimalType>(
129        capacity: usize,
130        decimal: DecimalDType,
131        nullability: Nullability,
132        allocator: &BufferAllocatorRef,
133    ) -> Self {
134        Self {
135            dtype: DType::Decimal(decimal, nullability),
136            values: match_each_decimal_value_type!(T::DECIMAL_TYPE, |D| {
137                DecimalBuffer::from(BufferMut::<D>::with_capacity_in(
138                    capacity,
139                    allocator.clone(),
140                ))
141            }),
142            nulls: LazyBitBufferBuilder::new(capacity, allocator.clone()),
143        }
144    }
145
146    /// Appends a decimal `value` to the builder.
147    pub fn append_value<V: NativeDecimalType>(&mut self, value: V) {
148        self.values.push(value);
149        self.nulls.append_non_null();
150    }
151
152    /// Appends `n` copies of `value` as non-null entries, directly writing into the buffer.
153    pub fn append_n_values<V: NativeDecimalType>(&mut self, value: V, n: usize) {
154        self.values.push_n(value, n);
155        self.nulls.append_n_non_nulls(n);
156    }
157
158    /// Appends the values of a canonical [`DecimalArray`] to the builder, coercing the physical
159    /// storage type to the builder's type as needed.
160    pub(crate) fn append_decimal_array(
161        &mut self,
162        array: &DecimalArray,
163        ctx: &mut ExecutionCtx,
164    ) -> VortexResult<()> {
165        match_each_decimal_value_type!(array.values_type(), |D| {
166            // Extends the values buffer from another buffer of type D where D can be coerced to the
167            // builder type.
168            self.values.extend(array.buffer::<D>().iter().copied());
169        });
170
171        self.nulls.append_validity_mask(
172            &array
173                .as_ref()
174                .validity()?
175                .execute_mask(array.as_ref().len(), ctx)?,
176        );
177        Ok(())
178    }
179
180    /// Finishes the builder directly into a [`DecimalArray`].
181    pub fn finish_into_decimal(&mut self) -> DecimalArray {
182        let validity = self.nulls.finish_with_nullability(self.dtype.nullability());
183
184        let decimal_dtype = *self.decimal_dtype();
185
186        delegate_fn!(self.values.take(), |T, values| {
187            DecimalArray::new::<T>(values.freeze(), decimal_dtype, validity)
188        })
189    }
190
191    /// The [`DecimalDType`] of this builder.
192    pub fn decimal_dtype(&self) -> &DecimalDType {
193        let DType::Decimal(decimal_dtype, _) = &self.dtype else {
194            vortex_panic!("`DecimalBuilder` somehow had dtype {}", self.dtype);
195        };
196
197        decimal_dtype
198    }
199}
200
201impl ArrayBuilder for DecimalBuilder {
202    fn as_any(&self) -> &dyn Any {
203        self
204    }
205
206    fn as_any_mut(&mut self) -> &mut dyn Any {
207        self
208    }
209
210    fn dtype(&self) -> &DType {
211        &self.dtype
212    }
213
214    fn len(&self) -> usize {
215        self.values.len()
216    }
217
218    fn append_zeros(&mut self, n: usize) {
219        self.values.push_n(0, n);
220        self.nulls.append_n_non_nulls(n);
221    }
222
223    unsafe fn append_nulls_unchecked(&mut self, n: usize) {
224        self.values.push_n(0, n);
225        self.nulls.append_n_nulls(n);
226    }
227
228    fn append_scalar(&mut self, scalar: &Scalar) -> VortexResult<()> {
229        vortex_ensure!(
230            scalar.dtype() == self.dtype(),
231            "DecimalBuilder expected scalar with dtype {}, got {}",
232            self.dtype(),
233            scalar.dtype()
234        );
235
236        match scalar.as_decimal().decimal_value() {
237            None => self.append_null(),
238            Some(v) => match_each_decimal_value!(v, |dec_val| {
239                self.append_value(dec_val);
240            }),
241        }
242
243        Ok(())
244    }
245
246    fn reserve_exact(&mut self, additional: usize) {
247        self.values.reserve(additional);
248        self.nulls.reserve_exact(additional);
249    }
250
251    fn finish(&mut self) -> ArrayRef {
252        self.finish_into_decimal().into_array()
253    }
254
255    fn finish_into_canonical(&mut self, _ctx: &mut ExecutionCtx) -> Canonical {
256        Canonical::Decimal(self.finish_into_decimal())
257    }
258}
259
260impl DecimalBuffer {
261    fn take(&mut self) -> Self {
262        delegate_fn!(self, |T, buffer| {
263            let allocator = buffer.allocator();
264            DecimalBuffer::from(std::mem::replace(buffer, allocator.with_capacity(0)))
265        })
266    }
267
268    fn push<V: NativeDecimalType>(&mut self, value: V) {
269        delegate_fn!(self, |T, buffer| {
270            buffer.push(
271                <T as BigCast>::from(value)
272                    .ok_or_else(|| {
273                        vortex_err!(
274                            "decimal conversion failure {:?}, type: {:?} to {:?}",
275                            value,
276                            V::DECIMAL_TYPE,
277                            T::DECIMAL_TYPE,
278                        )
279                    })
280                    .vortex_expect("operation should succeed in builder"),
281            )
282        });
283    }
284
285    fn push_n<V: NativeDecimalType>(&mut self, value: V, n: usize) {
286        delegate_fn!(self, |T, buffer| {
287            buffer.push_n(
288                <T as BigCast>::from(value).vortex_expect("decimal conversion failure"),
289                n,
290            )
291        });
292    }
293
294    fn reserve(&mut self, additional: usize) {
295        delegate_fn!(self, |T, buffer| { buffer.reserve(additional) })
296    }
297
298    fn len(&self) -> usize {
299        delegate_fn!(self, |T, buffer| { buffer.len() })
300    }
301
302    pub fn extend<I, V: NativeDecimalType>(&mut self, iter: I)
303    where
304        I: Iterator<Item = V>,
305    {
306        delegate_fn!(self, |T, buffer| {
307            buffer.extend(
308                iter.map(|x| <T as BigCast>::from(x).vortex_expect("decimal conversion failure")),
309            )
310        })
311    }
312}
313
314macro_rules! impl_from_buffer {
315    ($T:ty, $variant:ident) => {
316        impl From<BufferMut<$T>> for DecimalBuffer {
317            fn from(buffer: BufferMut<$T>) -> Self {
318                Self::$variant(buffer)
319            }
320        }
321    };
322}
323
324impl_from_buffer!(i8, I8);
325impl_from_buffer!(i16, I16);
326impl_from_buffer!(i32, I32);
327impl_from_buffer!(i64, I64);
328impl_from_buffer!(i128, I128);
329impl_from_buffer!(i256, I256);
330
331#[cfg(test)]
332mod tests {
333    use vortex_buffer::BufferAllocatorRef;
334
335    use crate::VortexSessionExecute;
336    use crate::array_session;
337    use crate::assert_arrays_eq;
338    use crate::builders::ArrayBuilder;
339    use crate::builders::DecimalBuilder;
340    use crate::builders::decimal::DecimalArray;
341    use crate::dtype::DecimalDType;
342
343    #[test]
344    fn test_mixed_extend() {
345        let values = 42i8;
346
347        let mut i8s = DecimalBuilder::new_in::<i8>(
348            DecimalDType::new(2, 1),
349            false.into(),
350            BufferAllocatorRef::static_ref(),
351        );
352        for v in 0..values {
353            i8s.append_value(v);
354        }
355        let i8s = i8s.finish();
356
357        let mut i128s = DecimalBuilder::new_in::<i128>(
358            DecimalDType::new(2, 1),
359            false.into(),
360            BufferAllocatorRef::static_ref(),
361        );
362        i8s.append_to_builder(&mut i128s, &mut array_session().create_execution_ctx())
363            .unwrap();
364        let i128s = i128s.finish();
365
366        for i in 0..i8s.len() {
367            assert_eq!(
368                i8s.execute_scalar(i, &mut array_session().create_execution_ctx())
369                    .unwrap(),
370                i128s
371                    .execute_scalar(i, &mut array_session().create_execution_ctx())
372                    .unwrap()
373            );
374        }
375    }
376
377    #[test]
378    fn test_append_scalar() {
379        let mut ctx = array_session().create_execution_ctx();
380        use crate::scalar::Scalar;
381
382        // Simply test that the builder accepts its own finish output via scalar.
383        let mut builder = DecimalBuilder::new_in::<i64>(
384            DecimalDType::new(10, 2),
385            true.into(),
386            BufferAllocatorRef::static_ref(),
387        );
388        builder.append_value(1234i64);
389        builder.append_value(5678i64);
390        builder.append_null();
391
392        let array = builder.finish();
393        let expected = DecimalArray::from_option_iter(
394            [Some(1234i64), Some(5678), None],
395            DecimalDType::new(10, 2),
396        );
397        assert_arrays_eq!(&array, &expected, &mut ctx);
398
399        // Test by taking a scalar from the array and appending it to a new builder.
400        let mut builder2 = DecimalBuilder::new_in::<i64>(
401            DecimalDType::new(10, 2),
402            true.into(),
403            BufferAllocatorRef::static_ref(),
404        );
405        for i in 0..array.len() {
406            let scalar = array
407                .execute_scalar(i, &mut array_session().create_execution_ctx())
408                .unwrap();
409            builder2.append_scalar(&scalar).unwrap();
410        }
411
412        let array2 = builder2.finish();
413        assert_arrays_eq!(&array2, &array, &mut ctx);
414
415        // Test wrong dtype error.
416        let mut builder = DecimalBuilder::new_in::<i64>(
417            DecimalDType::new(10, 2),
418            false.into(),
419            BufferAllocatorRef::static_ref(),
420        );
421        let wrong_scalar = Scalar::from(true);
422        assert!(builder.append_scalar(&wrong_scalar).is_err());
423    }
424}