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