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