Skip to main content

vortex_array/builders/
struct_.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::any::Any;
5
6use itertools::Itertools;
7use vortex_error::VortexExpect;
8use vortex_error::VortexResult;
9use vortex_error::vortex_bail;
10use vortex_error::vortex_ensure;
11use vortex_error::vortex_panic;
12use vortex_mask::Mask;
13
14use crate::ArrayRef;
15use crate::ExecutionCtx;
16use crate::IntoArray;
17use crate::arrays::StructArray;
18use crate::arrays::struct_::StructArrayExt;
19use crate::builders::ArrayBuilder;
20use crate::builders::DEFAULT_BUILDER_CAPACITY;
21use crate::builders::LazyBitBufferBuilder;
22use crate::builders::builder_with_capacity;
23use crate::canonical::Canonical;
24use crate::dtype::DType;
25use crate::dtype::Nullability;
26use crate::dtype::StructFields;
27use crate::scalar::Scalar;
28use crate::scalar::StructScalar;
29
30/// The builder for building a [`StructArray`].
31pub struct StructBuilder {
32    dtype: DType,
33    builders: Vec<Box<dyn ArrayBuilder>>,
34    nulls: LazyBitBufferBuilder,
35}
36
37impl StructBuilder {
38    /// Creates a new `StructBuilder` with a capacity of [`DEFAULT_BUILDER_CAPACITY`].
39    pub fn new(struct_dtype: StructFields, nullability: Nullability) -> Self {
40        Self::with_capacity(struct_dtype, nullability, DEFAULT_BUILDER_CAPACITY)
41    }
42
43    /// Creates a new `StructBuilder` with the given `capacity`.
44    pub fn with_capacity(
45        struct_dtype: StructFields,
46        nullability: Nullability,
47        capacity: usize,
48    ) -> Self {
49        let builders = struct_dtype
50            .fields()
51            .map(|dt| builder_with_capacity(&dt, capacity))
52            .collect();
53
54        Self {
55            builders,
56            nulls: LazyBitBufferBuilder::new(capacity),
57            dtype: DType::Struct(struct_dtype, nullability),
58        }
59    }
60
61    /// Appends a struct `value` to the builder.
62    pub fn append_value(&mut self, struct_scalar: StructScalar) -> VortexResult<()> {
63        if !self.dtype.is_nullable() && struct_scalar.is_null() {
64            vortex_bail!("Tried to append a null `StructScalar` to a non-nullable struct builder",);
65        }
66
67        if struct_scalar.struct_fields() != self.struct_fields() {
68            vortex_bail!(
69                "Tried to append a `StructScalar` with fields {} to a \
70                    struct builder with fields {}",
71                struct_scalar.struct_fields(),
72                self.struct_fields()
73            );
74        }
75
76        if let Some(fields) = struct_scalar.fields_iter() {
77            for (builder, field) in self.builders.iter_mut().zip_eq(fields) {
78                builder.append_scalar(&field)?;
79            }
80            self.nulls.append_non_null();
81        } else {
82            self.append_null()
83        }
84
85        Ok(())
86    }
87
88    /// Finishes the builder directly into a [`StructArray`].
89    pub fn finish_into_struct(&mut self) -> StructArray {
90        let len = self.len();
91        let fields = self
92            .builders
93            .iter_mut()
94            .map(|builder| builder.finish())
95            .collect::<Vec<_>>();
96
97        if fields.len() > 1 {
98            let expected_length = fields[0].len();
99            for (index, field) in fields[1..].iter().enumerate() {
100                assert_eq!(
101                    field.len(),
102                    expected_length,
103                    "Field {index} does not have expected length {expected_length}"
104                );
105            }
106        }
107
108        let validity = self.nulls.finish_with_nullability(self.dtype.nullability());
109
110        StructArray::try_new_with_dtype(fields, self.struct_fields().clone(), len, validity)
111            .vortex_expect("Fields must all have same length.")
112    }
113
114    /// The [`StructFields`] of this struct builder.
115    pub fn struct_fields(&self) -> &StructFields {
116        let DType::Struct(struct_fields, _) = &self.dtype else {
117            vortex_panic!("`StructBuilder` somehow had dtype {}", self.dtype);
118        };
119
120        struct_fields
121    }
122
123    /// Appends the values of a canonical [`StructArray`] to the builder, recursing into each
124    /// field's builder.
125    pub(crate) fn append_struct_array(
126        &mut self,
127        array: &StructArray,
128        ctx: &mut ExecutionCtx,
129    ) -> VortexResult<()> {
130        for (field, builder) in array
131            .iter_unmasked_fields()
132            .zip_eq(self.builders.iter_mut())
133        {
134            field.append_to_builder(builder.as_mut(), ctx)?;
135        }
136
137        self.nulls
138            .append_validity_mask(&array.validity()?.execute_mask(array.len(), ctx)?);
139        Ok(())
140    }
141}
142
143impl ArrayBuilder for StructBuilder {
144    fn as_any(&self) -> &dyn Any {
145        self
146    }
147
148    fn as_any_mut(&mut self) -> &mut dyn Any {
149        self
150    }
151
152    fn dtype(&self) -> &DType {
153        &self.dtype
154    }
155
156    fn len(&self) -> usize {
157        self.nulls.len()
158    }
159
160    fn append_zeros(&mut self, n: usize) {
161        self.builders
162            .iter_mut()
163            .for_each(|builder| builder.append_zeros(n));
164        self.nulls.append_n_non_nulls(n);
165    }
166
167    unsafe fn append_nulls_unchecked(&mut self, n: usize) {
168        self.builders
169            .iter_mut()
170            // We push zero values into our children when appending a null in case the children are
171            // themselves non-nullable.
172            .for_each(|builder| builder.append_defaults(n));
173        self.nulls.append_n_nulls(n);
174    }
175
176    fn append_scalar(&mut self, scalar: &Scalar) -> VortexResult<()> {
177        vortex_ensure!(
178            scalar.dtype() == self.dtype(),
179            "StructBuilder expected scalar with dtype {}, got {}",
180            self.dtype(),
181            scalar.dtype()
182        );
183
184        self.append_value(scalar.as_struct())
185    }
186
187    fn reserve_exact(&mut self, capacity: usize) {
188        self.builders.iter_mut().for_each(|builder| {
189            builder.reserve_exact(capacity);
190        });
191        self.nulls.reserve_exact(capacity);
192    }
193
194    unsafe fn set_validity_unchecked(&mut self, validity: Mask) {
195        self.nulls = LazyBitBufferBuilder::from_validity_mask(validity);
196    }
197
198    fn finish(&mut self) -> ArrayRef {
199        self.finish_into_struct().into_array()
200    }
201
202    fn finish_into_canonical(&mut self, _ctx: &mut ExecutionCtx) -> Canonical {
203        Canonical::Struct(self.finish_into_struct())
204    }
205}
206
207#[cfg(test)]
208mod tests {
209    use crate::IntoArray;
210    use crate::VortexSessionExecute;
211    use crate::array_session;
212    use crate::arrays::PrimitiveArray;
213    use crate::arrays::VarBinArray;
214    use crate::assert_arrays_eq;
215    use crate::builders::ArrayBuilder;
216    use crate::builders::struct_::StructArray;
217    use crate::builders::struct_::StructBuilder;
218    use crate::dtype::DType;
219    use crate::dtype::Nullability;
220    use crate::dtype::PType::I32;
221    use crate::dtype::StructFields;
222    use crate::scalar::Scalar;
223    use crate::validity::Validity;
224
225    #[test]
226    fn test_struct_builder() {
227        let sdt = StructFields::new(["a", "b"].into(), vec![I32.into(), I32.into()]);
228        let dtype = DType::Struct(sdt.clone(), Nullability::NonNullable);
229        let mut builder = StructBuilder::with_capacity(sdt, Nullability::NonNullable, 0);
230
231        builder
232            .append_value(Scalar::struct_(dtype.clone(), vec![1.into(), 2.into()]).as_struct())
233            .unwrap();
234
235        let struct_ = builder.finish();
236        assert_eq!(struct_.len(), 1);
237        assert_eq!(struct_.dtype(), &dtype);
238    }
239
240    #[test]
241    fn test_append_nullable_struct() {
242        let sdt = StructFields::new(["a", "b"].into(), vec![I32.into(), I32.into()]);
243        let dtype = DType::Struct(sdt.clone(), Nullability::Nullable);
244        let mut builder = StructBuilder::with_capacity(sdt, Nullability::Nullable, 0);
245
246        builder
247            .append_value(Scalar::struct_(dtype.clone(), vec![1.into(), 2.into()]).as_struct())
248            .unwrap();
249
250        builder.append_nulls(2);
251
252        let struct_ = builder.finish();
253        assert_eq!(struct_.len(), 3);
254        assert_eq!(struct_.dtype(), &dtype);
255        assert_eq!(
256            struct_
257                .valid_count(&mut array_session().create_execution_ctx())
258                .unwrap(),
259            1
260        );
261    }
262
263    #[test]
264    fn test_append_scalar() {
265        let mut ctx = array_session().create_execution_ctx();
266        use crate::scalar::Scalar;
267
268        let dtype = DType::Struct(
269            StructFields::from_iter([
270                ("a", DType::Primitive(I32, Nullability::Nullable)),
271                ("b", DType::Utf8(Nullability::Nullable)),
272            ]),
273            Nullability::Nullable,
274        );
275
276        let struct_fields = match &dtype {
277            DType::Struct(fields, _) => fields.clone(),
278            _ => panic!("Expected struct dtype"),
279        };
280        let mut builder = StructBuilder::new(struct_fields, Nullability::Nullable);
281
282        // Test appending a valid struct value.
283        let struct_scalar1 = Scalar::struct_(
284            dtype.clone(),
285            vec![
286                Scalar::primitive(42i32, Nullability::Nullable),
287                Scalar::utf8("hello", Nullability::Nullable),
288            ],
289        );
290        builder.append_scalar(&struct_scalar1).unwrap();
291
292        // Test appending another struct value.
293        let struct_scalar2 = Scalar::struct_(
294            dtype.clone(),
295            vec![
296                Scalar::primitive(84i32, Nullability::Nullable),
297                Scalar::utf8("world", Nullability::Nullable),
298            ],
299        );
300        builder.append_scalar(&struct_scalar2).unwrap();
301
302        // Test appending null value.
303        let null_scalar = Scalar::null(dtype.clone());
304        builder.append_scalar(&null_scalar).unwrap();
305
306        let array = builder.finish_into_struct();
307
308        let expected = StructArray::try_from_iter_with_validity(
309            [
310                (
311                    "a",
312                    PrimitiveArray::from_option_iter([Some(42i32), Some(84), Some(123)])
313                        .into_array(),
314                ),
315                (
316                    "b",
317                    <VarBinArray as FromIterator<_>>::from_iter([
318                        Some("hello"),
319                        Some("world"),
320                        Some("x"),
321                    ])
322                    .into_array(),
323                ),
324            ],
325            Validity::from_iter([true, true, false]),
326        )
327        .unwrap();
328        assert_arrays_eq!(&array, &expected, &mut ctx);
329
330        // Test wrong dtype error.
331        let struct_fields = match &dtype {
332            DType::Struct(fields, _) => fields.clone(),
333            _ => panic!("Expected struct dtype"),
334        };
335        let mut builder = StructBuilder::new(struct_fields, Nullability::NonNullable);
336        let wrong_scalar = Scalar::from(42i32);
337        assert!(builder.append_scalar(&wrong_scalar).is_err());
338    }
339}