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