Skip to main content

vortex_array/builders/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4//! Builders for Vortex arrays.
5//!
6//! Every logical type in Vortex has a canonical (uncompressed) in-memory encoding. This module
7//! provides pre-allocated builders to construct new canonical arrays.
8//!
9//! Canonical form is not recursive, and neither are these builders: appending an array to a nested
10//! builder keeps the child in the encoding it arrived in instead of decoding it. The fields of a
11//! [`StructArray`](crate::arrays::StructArray), the elements of a list, and the storage of an
12//! [`ExtensionArray`](crate::arrays::ExtensionArray) may therefore come back compressed, or as a
13//! [`ChunkedArray`](crate::arrays::ChunkedArray) when several arrays were appended in turn.
14//!
15//! ## Example:
16//!
17//! ```
18//! use vortex_array::builders::{ArrayBuilder, builder_with_capacity_in};
19//! use vortex_array::dtype::{DType, Nullability};
20//! use vortex_array::memory::BufferAllocatorRef;
21//! use vortex_array::{VortexSessionExecute, array_session};
22//!
23//! // Create a new builder for string data.
24//! let mut builder = builder_with_capacity_in(&DType::Utf8(Nullability::NonNullable), 4, BufferAllocatorRef::static_ref());
25//!
26//! builder.append_scalar(&"a".into()).unwrap();
27//! builder.append_scalar(&"b".into()).unwrap();
28//! builder.append_scalar(&"c".into()).unwrap();
29//! builder.append_scalar(&"d".into()).unwrap();
30//!
31//! let strings = builder.finish();
32//! let mut ctx = array_session().create_execution_ctx();
33//!
34//! assert_eq!(strings.execute_scalar(0, &mut ctx).unwrap(), "a".into());
35//! assert_eq!(strings.execute_scalar(1, &mut ctx).unwrap(), "b".into());
36//! assert_eq!(strings.execute_scalar(2, &mut ctx).unwrap(), "c".into());
37//! assert_eq!(strings.execute_scalar(3, &mut ctx).unwrap(), "d".into());
38//! ```
39
40use std::any::Any;
41use std::sync::Arc;
42
43use vortex_error::VortexResult;
44
45use crate::ArrayRef;
46use crate::ExecutionCtx;
47use crate::canonical::Canonical;
48use crate::dtype::DType;
49use crate::match_each_decimal_value_type;
50use crate::match_each_native_ptype;
51use crate::memory::BufferAllocatorRef;
52use crate::scalar::Scalar;
53
54mod lazy_null_builder;
55pub(crate) use lazy_null_builder::LazyBitBufferBuilder;
56
57mod bool;
58mod child;
59mod decimal;
60pub mod dict;
61mod extension;
62mod fixed_size_list;
63mod list;
64mod listview;
65mod map;
66mod null;
67mod primitive;
68mod struct_;
69mod validity;
70mod varbinview;
71
72pub use bool::*;
73pub(crate) use child::ChildBuilder;
74pub use decimal::*;
75pub use extension::*;
76pub use fixed_size_list::*;
77pub use list::*;
78pub use listview::*;
79pub use map::*;
80pub use null::*;
81pub use primitive::*;
82pub use struct_::*;
83pub(crate) use validity::ValidityBuilder;
84pub use varbinview::*;
85
86pub use crate::arrays::varbin::builder::VarBinBuilder;
87
88#[cfg(test)]
89mod tests;
90
91/// The default capacity for builders.
92///
93/// This is equal to the default capacity for Arrow Arrays.
94pub const DEFAULT_BUILDER_CAPACITY: usize = 1024;
95
96pub trait ArrayBuilder: Send {
97    fn as_any(&self) -> &dyn Any;
98
99    fn as_any_mut(&mut self) -> &mut dyn Any;
100
101    fn dtype(&self) -> &DType;
102
103    fn len(&self) -> usize;
104
105    fn is_empty(&self) -> bool {
106        self.len() == 0
107    }
108
109    /// Append a "zero" value to the array.
110    ///
111    /// Zero values are generally determined by [`Scalar::default_value`].
112    fn append_zero(&mut self) {
113        self.append_zeros(1)
114    }
115
116    /// Appends n "zero" values to the array.
117    ///
118    /// Zero values are generally determined by [`Scalar::default_value`].
119    fn append_zeros(&mut self, n: usize);
120
121    /// Append a "null" value to the array.
122    ///
123    /// Implementors should panic if this method is called on a non-nullable [`ArrayBuilder`].
124    fn append_null(&mut self) {
125        self.append_nulls(1)
126    }
127
128    /// The inner part of `append_nulls`.
129    ///
130    /// # Safety
131    ///
132    /// The array builder must be nullable.
133    unsafe fn append_nulls_unchecked(&mut self, n: usize);
134
135    /// Appends n "null" values to the array.
136    ///
137    /// Implementors should panic if this method is called on a non-nullable [`ArrayBuilder`].
138    fn append_nulls(&mut self, n: usize) {
139        assert!(
140            self.dtype().is_nullable(),
141            "tried to append {n} nulls to a non-nullable array builder"
142        );
143
144        // SAFETY: We check above that the array builder is nullable.
145        unsafe {
146            self.append_nulls_unchecked(n);
147        }
148    }
149
150    /// Appends a default value to the array.
151    fn append_default(&mut self) {
152        self.append_defaults(1)
153    }
154
155    /// Appends n default values to the array.
156    ///
157    /// If the array builder is nullable, then this has the behavior of `self.append_nulls(n)`.
158    /// If the array builder is non-nullable, then it has the behavior of `self.append_zeros(n)`.
159    fn append_defaults(&mut self, n: usize) {
160        if self.dtype().is_nullable() {
161            self.append_nulls(n);
162        } else {
163            self.append_zeros(n);
164        }
165    }
166
167    /// A generic function to append a scalar to the builder.
168    fn append_scalar(&mut self, scalar: &Scalar) -> VortexResult<()>;
169
170    /// Allocate space for extra `additional` items
171    fn reserve_exact(&mut self, additional: usize);
172
173    /// Constructs an Array from the builder components.
174    ///
175    /// The returned array is canonical at the top level only; its children keep whatever encoding
176    /// they were appended with.
177    ///
178    /// # Panics
179    ///
180    /// This function may panic if the builder's methods are called with invalid arguments. If only
181    /// the methods on this interface are used, the builder should not panic. However, specific
182    /// builders have interfaces that may be misused. For example, if the number of values in a
183    /// [PrimitiveBuilder]'s [vortex_buffer::BufferMut] does not match the number of validity bits,
184    /// the PrimitiveBuilder's [Self::finish] will panic.
185    fn finish(&mut self) -> ArrayRef;
186
187    /// Constructs a canonical array directly from the builder.
188    ///
189    /// This method provides a default implementation that creates an [`ArrayRef`] via `finish` and
190    /// then converts it to canonical form. Specific builders can override this with optimized
191    /// implementations that avoid the intermediate [`ArrayRef`] creation.
192    fn finish_into_canonical(&mut self, ctx: &mut ExecutionCtx) -> Canonical;
193}
194
195/// Matches a `&mut dyn ArrayBuilder` against every concrete list builder type, i.e. every
196/// [`ListBuilder`]`<O>` and [`ListViewBuilder`]`<O, S>` instantiation over the
197/// [`OffsetBuilderPType`](crate::dtype::OffsetBuilderPType) offset/size types (`u32`, `u64`, `i32`,
198/// `i64`).
199///
200/// Binds the downcast builder as `$builder` and evaluates `$body` with it, yielding
201/// `Some($body)`; yields `None` when the builder is not a list builder. List encodings dispatch
202/// through this matcher because the concrete list builders are generic over their offset/size
203/// integer types, which cannot be named through a `dyn ArrayBuilder`. The matcher is exhaustive
204/// because `OffsetBuilderPType` is sealed, so no other instantiations can be constructed.
205#[macro_export]
206macro_rules! match_each_list_builder {
207    ($dyn_builder:expr, | $builder:ident | $body:expr) => {{
208        let __dyn_builder: &mut dyn $crate::builders::ArrayBuilder = $dyn_builder;
209        match $crate::__match_each_list_builder!(
210            __dyn_builder,
211            $builder,
212            $body,
213            [u32, u64, i32, i64]
214        ) {
215            ::core::option::Option::Some(__result) => ::core::option::Option::Some(__result),
216            ::core::option::Option::None => $crate::__match_each_listview_builder!(
217                __dyn_builder,
218                $builder,
219                $body,
220                [u32, u64, i32, i64]
221            ),
222        }
223    }};
224}
225
226/// Matches a `&mut dyn ArrayBuilder` against every concrete [`ListViewBuilder`]`<O, S>`
227/// instantiation over the [`OffsetBuilderPType`](crate::dtype::OffsetBuilderPType) offset/size
228/// types (`u32`, `u64`, `i32`, `i64`), and only those.
229///
230/// Binds the downcast builder as `$builder` and evaluates `$body` with it, yielding
231/// `Some($body)`; yields `None` when the builder is not a list-view builder - including when it
232/// is a [`ListBuilder`]. Callers reach for this instead of
233/// [`match_each_list_builder!`](crate::match_each_list_builder) when the body needs methods only
234/// a list-view builder has, such as
235/// [`append_array_as_repeated_list`](ListViewBuilder::append_array_as_repeated_list).
236#[macro_export]
237macro_rules! match_each_listview_builder {
238    ($dyn_builder:expr, | $builder:ident | $body:expr) => {{
239        let __dyn_builder: &mut dyn $crate::builders::ArrayBuilder = $dyn_builder;
240        $crate::__match_each_listview_builder!(__dyn_builder, $builder, $body, [u32, u64, i32, i64])
241    }};
242}
243
244#[doc(hidden)]
245#[macro_export]
246macro_rules! __match_each_list_builder {
247    ($target:ident, $builder:ident, $body:expr, []) => {
248        ::core::option::Option::None
249    };
250    ($target:ident, $builder:ident, $body:expr, [$offset:ty $(, $rest:ty)*]) => {
251        if let ::core::option::Option::Some($builder) =
252            $crate::builders::ArrayBuilder::as_any_mut($target)
253                .downcast_mut::<$crate::builders::ListBuilder<$offset>>()
254        {
255            ::core::option::Option::Some($body)
256        } else {
257            $crate::__match_each_list_builder!($target, $builder, $body, [$($rest),*])
258        }
259    };
260}
261
262#[doc(hidden)]
263#[macro_export]
264macro_rules! __match_each_listview_builder {
265    ($target:ident, $builder:ident, $body:expr, []) => {
266        ::core::option::Option::None
267    };
268    ($target:ident, $builder:ident, $body:expr, [$offset:ty $(, $rest:ty)*]) => {
269        match $crate::__match_each_listview_builder_size!(
270            $target,
271            $builder,
272            $body,
273            $offset,
274            [u32, u64, i32, i64]
275        ) {
276            ::core::option::Option::Some(__result) => ::core::option::Option::Some(__result),
277            ::core::option::Option::None => $crate::__match_each_listview_builder!(
278                $target,
279                $builder,
280                $body,
281                [$($rest),*]
282            ),
283        }
284    };
285}
286
287#[doc(hidden)]
288#[macro_export]
289macro_rules! __match_each_listview_builder_size {
290    ($target:ident, $builder:ident, $body:expr, $offset:ty, []) => {
291        ::core::option::Option::None
292    };
293    ($target:ident, $builder:ident, $body:expr, $offset:ty, [$size:ty $(, $rest:ty)*]) => {
294        if let ::core::option::Option::Some($builder) =
295            $crate::builders::ArrayBuilder::as_any_mut($target)
296                .downcast_mut::<$crate::builders::ListViewBuilder<$offset, $size>>()
297        {
298            ::core::option::Option::Some($body)
299        } else {
300            $crate::__match_each_listview_builder_size!(
301                $target, $builder, $body, $offset, [$($rest),*]
302            )
303        }
304    };
305}
306
307/// Matches a `&mut dyn ArrayBuilder` against every concrete map builder type.
308///
309/// Binds the downcast builder as `$builder` and evaluates `$body` with it, yielding
310/// `Some($body)`; yields `None` when the builder is not a map builder.
311#[macro_export]
312macro_rules! match_each_map_builder {
313    ($dyn_builder:expr, | $builder:ident | $body:expr) => {{
314        let __dyn_builder: &mut dyn $crate::builders::ArrayBuilder = $dyn_builder;
315        $crate::__match_each_map_builder!(__dyn_builder, $builder, $body, [u32, u64, i32, i64])
316    }};
317}
318
319#[doc(hidden)]
320#[macro_export]
321macro_rules! __match_each_map_builder {
322    ($target:ident, $builder:ident, $body:expr, []) => {
323        ::core::option::Option::None
324    };
325    ($target:ident, $builder:ident, $body:expr, [$offset:ty $(, $rest:ty)*]) => {
326        match $crate::__match_each_map_builder_size!(
327            $target,
328            $builder,
329            $body,
330            $offset,
331            [u32, u64, i32, i64]
332        ) {
333            ::core::option::Option::Some(__result) => ::core::option::Option::Some(__result),
334            ::core::option::Option::None => $crate::__match_each_map_builder!(
335                $target,
336                $builder,
337                $body,
338                [$($rest),*]
339            ),
340        }
341    };
342}
343
344#[doc(hidden)]
345#[macro_export]
346macro_rules! __match_each_map_builder_size {
347    ($target:ident, $builder:ident, $body:expr, $offset:ty, []) => {
348        ::core::option::Option::None
349    };
350    ($target:ident, $builder:ident, $body:expr, $offset:ty, [$size:ty $(, $rest:ty)*]) => {
351        if let ::core::option::Option::Some($builder) =
352            $crate::builders::ArrayBuilder::as_any_mut($target)
353                .downcast_mut::<$crate::builders::MapBuilder<$offset, $size>>()
354        {
355            ::core::option::Option::Some($body)
356        } else {
357            $crate::__match_each_map_builder_size!(
358                $target, $builder, $body, $offset, [$($rest),*]
359            )
360        }
361    };
362}
363
364/// Construct a new canonical builder for the given [`DType`].
365#[deprecated(note = "use `builder_with_capacity_in` with an explicit allocator")]
366pub fn builder_with_capacity(dtype: &DType, capacity: usize) -> Box<dyn ArrayBuilder> {
367    builder_with_capacity_in(dtype, capacity, BufferAllocatorRef::static_ref())
368}
369
370/// Construct a new canonical builder using `allocator`.
371///
372/// # Example
373///
374/// ```
375/// use vortex_array::builders::{ArrayBuilder, builder_with_capacity_in};
376/// use vortex_array::dtype::{DType, Nullability};
377/// use vortex_array::memory::BufferAllocatorRef;
378/// use vortex_array::{VortexSessionExecute, array_session};
379///
380/// // Create a new builder for string data.
381/// let mut builder = builder_with_capacity_in(&DType::Utf8(Nullability::NonNullable), 4, BufferAllocatorRef::static_ref());
382///
383/// builder.append_scalar(&"a".into()).unwrap();
384/// builder.append_scalar(&"b".into()).unwrap();
385/// builder.append_scalar(&"c".into()).unwrap();
386/// builder.append_scalar(&"d".into()).unwrap();
387///
388/// let strings = builder.finish();
389/// let mut ctx = array_session().create_execution_ctx();
390///
391/// assert_eq!(strings.execute_scalar(0, &mut ctx).unwrap(), "a".into());
392/// assert_eq!(strings.execute_scalar(1, &mut ctx).unwrap(), "b".into());
393/// assert_eq!(strings.execute_scalar(2, &mut ctx).unwrap(), "c".into());
394/// assert_eq!(strings.execute_scalar(3, &mut ctx).unwrap(), "d".into());
395/// ```
396pub fn builder_with_capacity_in(
397    dtype: &DType,
398    capacity: usize,
399    allocator: &BufferAllocatorRef,
400) -> Box<dyn ArrayBuilder> {
401    match dtype {
402        DType::Null => Box::new(NullBuilder::new()),
403        DType::Bool(n) => Box::new(BoolBuilder::with_capacity_in(*n, capacity, allocator)),
404        DType::Primitive(ptype, n) => {
405            match_each_native_ptype!(ptype, |P| {
406                Box::new(PrimitiveBuilder::<P>::with_capacity_in(
407                    *n, capacity, allocator,
408                ))
409            })
410        }
411        DType::Decimal(decimal_type, n) => {
412            match_each_decimal_value_type!(
413                DecimalType::smallest_decimal_value_type(decimal_type),
414                |D| {
415                    Box::new(DecimalBuilder::with_capacity_in::<D>(
416                        capacity,
417                        *decimal_type,
418                        *n,
419                        allocator,
420                    ))
421                }
422            )
423        }
424        DType::Utf8(n) => Box::new(VarBinViewBuilder::with_capacity_in(
425            DType::Utf8(*n),
426            capacity,
427            allocator.clone(),
428        )),
429        DType::Binary(n) => Box::new(VarBinViewBuilder::with_capacity_in(
430            DType::Binary(*n),
431            capacity,
432            allocator.clone(),
433        )),
434        DType::List(dtype, n) => Box::new(ListViewBuilder::<u64, u64>::with_capacity_in(
435            Arc::clone(dtype),
436            *n,
437            2 * capacity, // Arbitrarily choose 2 times the `offsets` capacity here.
438            capacity,
439            allocator,
440        )),
441        DType::Map(map_dtype, nullability) => Box::new(MapBuilder::<u64, u64>::with_capacity_in(
442            map_dtype.clone(),
443            *nullability,
444            capacity,
445            allocator,
446        )),
447        DType::FixedSizeList(elem_dtype, list_size, null) => {
448            Box::new(FixedSizeListBuilder::with_capacity_in(
449                Arc::clone(elem_dtype),
450                *list_size,
451                *null,
452                capacity,
453                allocator,
454            ))
455        }
456        DType::Struct(struct_dtype, n) => Box::new(StructBuilder::with_capacity_in(
457            struct_dtype.clone(),
458            *n,
459            capacity,
460            allocator,
461        )),
462        DType::Union(..) => todo!("TODO(connor)[Union]: unimplemented"),
463        DType::Variant(_) => {
464            unimplemented!()
465        }
466        DType::Extension(ext_dtype) => Box::new(ExtensionBuilder::with_capacity_in(
467            ext_dtype.clone(),
468            capacity,
469            allocator,
470        )),
471    }
472}