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