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;
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!(
208            scalar.dtype() == self.dtype(),
209            "StructBuilder expected scalar with dtype {}, got {}",
210            self.dtype(),
211            scalar.dtype()
212        );
213
214        self.append_value(scalar.as_struct())
215    }
216
217    fn reserve_exact(&mut self, capacity: usize) {
218        self.builders.iter_mut().for_each(|builder| {
219            builder.reserve_exact(capacity);
220        });
221        self.nulls.reserve_exact(capacity);
222    }
223
224    fn finish(&mut self) -> ArrayRef {
225        self.finish_into_struct().into_array()
226    }
227
228    fn finish_into_canonical(&mut self, _ctx: &mut ExecutionCtx) -> Canonical {
229        Canonical::Struct(self.finish_into_struct())
230    }
231}
232
233#[cfg(test)]
234mod tests {
235    use vortex_buffer::BufferAllocatorRef;
236
237    use crate::IntoArray;
238    use crate::VortexSessionExecute;
239    use crate::array_session;
240    use crate::arrays::PrimitiveArray;
241    use crate::arrays::VarBinArray;
242    use crate::assert_arrays_eq;
243    use crate::builders::ArrayBuilder;
244    use crate::builders::struct_::StructArray;
245    use crate::builders::struct_::StructBuilder;
246    use crate::dtype::DType;
247    use crate::dtype::Nullability;
248    use crate::dtype::PType::I32;
249    use crate::dtype::StructFields;
250    use crate::scalar::Scalar;
251    use crate::validity::Validity;
252
253    #[test]
254    fn test_struct_builder() {
255        let sdt = StructFields::new(["a", "b"].into(), vec![I32.into(), I32.into()]);
256        let dtype = DType::Struct(sdt.clone(), Nullability::NonNullable);
257        let mut builder = StructBuilder::with_capacity_in(
258            sdt,
259            Nullability::NonNullable,
260            0,
261            BufferAllocatorRef::static_ref(),
262        );
263
264        builder
265            .append_value(Scalar::struct_(dtype.clone(), vec![1.into(), 2.into()]).as_struct())
266            .unwrap();
267
268        let struct_ = builder.finish();
269        assert_eq!(struct_.len(), 1);
270        assert_eq!(struct_.dtype(), &dtype);
271    }
272
273    #[test]
274    fn test_append_nullable_struct() {
275        let sdt = StructFields::new(["a", "b"].into(), vec![I32.into(), I32.into()]);
276        let dtype = DType::Struct(sdt.clone(), Nullability::Nullable);
277        let mut builder = StructBuilder::with_capacity_in(
278            sdt,
279            Nullability::Nullable,
280            0,
281            BufferAllocatorRef::static_ref(),
282        );
283
284        builder
285            .append_value(Scalar::struct_(dtype.clone(), vec![1.into(), 2.into()]).as_struct())
286            .unwrap();
287
288        builder.append_nulls(2);
289
290        let struct_ = builder.finish();
291        assert_eq!(struct_.len(), 3);
292        assert_eq!(struct_.dtype(), &dtype);
293        assert_eq!(
294            struct_
295                .valid_count(&mut array_session().create_execution_ctx())
296                .unwrap(),
297            1
298        );
299    }
300
301    #[test]
302    fn test_append_scalar() {
303        let mut ctx = array_session().create_execution_ctx();
304        use crate::scalar::Scalar;
305
306        let dtype = DType::Struct(
307            StructFields::from_iter([
308                ("a", DType::Primitive(I32, Nullability::Nullable)),
309                ("b", DType::Utf8(Nullability::Nullable)),
310            ]),
311            Nullability::Nullable,
312        );
313
314        let struct_fields = match &dtype {
315            DType::Struct(fields, _) => fields.clone(),
316            _ => panic!("Expected struct dtype"),
317        };
318        let mut builder = StructBuilder::new_in(
319            struct_fields,
320            Nullability::Nullable,
321            BufferAllocatorRef::static_ref(),
322        );
323
324        // Test appending a valid struct value.
325        let struct_scalar1 = Scalar::struct_(
326            dtype.clone(),
327            vec![
328                Scalar::primitive(42i32, Nullability::Nullable),
329                Scalar::utf8("hello", Nullability::Nullable),
330            ],
331        );
332        builder.append_scalar(&struct_scalar1).unwrap();
333
334        // Test appending another struct value.
335        let struct_scalar2 = Scalar::struct_(
336            dtype.clone(),
337            vec![
338                Scalar::primitive(84i32, Nullability::Nullable),
339                Scalar::utf8("world", Nullability::Nullable),
340            ],
341        );
342        builder.append_scalar(&struct_scalar2).unwrap();
343
344        // Test appending null value.
345        let null_scalar = Scalar::null(dtype.clone());
346        builder.append_scalar(&null_scalar).unwrap();
347
348        let array = builder.finish_into_struct();
349
350        let expected = StructArray::try_from_iter_with_validity(
351            [
352                (
353                    "a",
354                    PrimitiveArray::from_option_iter([Some(42i32), Some(84), Some(123)])
355                        .into_array(),
356                ),
357                (
358                    "b",
359                    <VarBinArray as FromIterator<_>>::from_iter([
360                        Some("hello"),
361                        Some("world"),
362                        Some("x"),
363                    ])
364                    .into_array(),
365                ),
366            ],
367            Validity::from_iter([true, true, false]),
368        )
369        .unwrap();
370        assert_arrays_eq!(&array, &expected, &mut ctx);
371
372        // Test wrong dtype error.
373        let struct_fields = match &dtype {
374            DType::Struct(fields, _) => fields.clone(),
375            _ => panic!("Expected struct dtype"),
376        };
377        let mut builder = StructBuilder::new_in(
378            struct_fields,
379            Nullability::NonNullable,
380            BufferAllocatorRef::static_ref(),
381        );
382        let wrong_scalar = Scalar::from(42i32);
383        assert!(builder.append_scalar(&wrong_scalar).is_err());
384    }
385}