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