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