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