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}