@@ -18,13 +18,16 @@ use crate::dtype::Nullability;
1818use crate :: scalar:: Scalar ;
1919use crate :: validity:: Validity ;
2020
21- /// The shortest array that a [`ChildBuilder`] keeps as a chunk of its own.
21+ /// The default shortest array that a [`ChildBuilder`] keeps as a chunk of its own.
2222///
2323/// Nested builders routinely append very short arrays (the elements of a single list, for
2424/// example), and giving each one its own chunk would produce a [`ChunkedArray`] with more chunks
2525/// than values. Below this length, copying the values costs less than the indirection that every
2626/// later access to the chunk would pay.
27- pub ( super ) const MIN_CHUNK_LEN : usize = 64 ;
27+ ///
28+ /// Callers that would rather keep every chunk boundary, however short, can lower the threshold
29+ /// with [`ArrayBuilder::set_min_chunk_len`].
30+ pub ( super ) const DEFAULT_MIN_CHUNK_LEN : usize = 64 ;
2831
2932/// Accumulates the child of a nested [`ArrayBuilder`] without canonicalizing appended arrays.
3033///
@@ -51,6 +54,9 @@ pub struct ChildBuilder {
5154
5255 /// Builder holding the scalars appended after the last chunk.
5356 pending : Box < dyn ArrayBuilder > ,
57+
58+ /// The shortest appended array that is kept as a chunk rather than copied.
59+ min_chunk_len : usize ,
5460}
5561
5662impl ChildBuilder {
@@ -61,9 +67,18 @@ impl ChildBuilder {
6167 chunks : Vec :: new ( ) ,
6268 chunks_len : 0 ,
6369 pending : builder_with_capacity ( dtype, capacity) ,
70+ min_chunk_len : DEFAULT_MIN_CHUNK_LEN ,
6471 }
6572 }
6673
74+ /// Sets the shortest appended array that is kept as a chunk rather than copied.
75+ ///
76+ /// See [`ArrayBuilder::set_min_chunk_len`].
77+ pub fn set_min_chunk_len ( & mut self , min_chunk_len : usize ) {
78+ self . min_chunk_len = min_chunk_len;
79+ self . pending . set_min_chunk_len ( min_chunk_len) ;
80+ }
81+
6782 /// The number of values appended so far.
6883 pub fn len ( & self ) -> usize {
6984 self . chunks_len + self . pending . len ( )
@@ -85,7 +100,7 @@ impl ChildBuilder {
85100 return Ok ( ( ) ) ;
86101 }
87102
88- if array. len ( ) < MIN_CHUNK_LEN {
103+ if array. len ( ) < self . min_chunk_len {
89104 return array. append_to_builder ( self . pending . as_mut ( ) , ctx) ;
90105 }
91106
@@ -196,14 +211,16 @@ impl ChildBuilder {
196211
197212#[ cfg( test) ]
198213mod tests {
214+ use std:: sync:: Arc ;
215+
199216 use rstest:: rstest;
200217 use vortex_buffer:: buffer;
201218 use vortex_error:: VortexExpect ;
202219 use vortex_error:: VortexResult ;
203220 use vortex_mask:: Mask ;
204221
205222 use super :: ChildBuilder ;
206- use super :: MIN_CHUNK_LEN ;
223+ use super :: DEFAULT_MIN_CHUNK_LEN as MIN_CHUNK_LEN ;
207224 use crate :: ArrayRef ;
208225 use crate :: IntoArray ;
209226 use crate :: VortexSessionExecute ;
@@ -212,16 +229,20 @@ mod tests {
212229 use crate :: arrays:: ChunkedArray ;
213230 use crate :: arrays:: Constant ;
214231 use crate :: arrays:: ConstantArray ;
232+ use crate :: arrays:: ListView ;
233+ use crate :: arrays:: ListViewArray ;
215234 use crate :: arrays:: Masked ;
216235 use crate :: arrays:: Primitive ;
217236 use crate :: arrays:: PrimitiveArray ;
218237 use crate :: arrays:: chunked:: ChunkedArrayExt ;
238+ use crate :: arrays:: listview:: ListViewArraySlotsExt ;
219239 use crate :: assert_arrays_eq;
220240 use crate :: dtype:: DType ;
221241 use crate :: dtype:: Nullability :: NonNullable ;
222242 use crate :: dtype:: Nullability :: Nullable ;
223243 use crate :: dtype:: PType :: I32 ;
224244 use crate :: scalar:: Scalar ;
245+ use crate :: validity:: Validity ;
225246
226247 /// A non-canonical array of `len` values, all equal to `value`.
227248 fn constant ( value : i32 , len : usize ) -> ArrayRef {
@@ -476,6 +497,50 @@ mod tests {
476497 unsafe { builder. set_validity_unchecked ( Mask :: new_true ( MIN_CHUNK_LEN ) ) } ;
477498 }
478499
500+ /// A threshold of zero keeps every chunk boundary, however short.
501+ #[ test]
502+ fn test_min_chunk_len_zero_keeps_every_chunk ( ) -> VortexResult < ( ) > {
503+ let mut ctx = array_session ( ) . create_execution_ctx ( ) ;
504+ let mut builder = ChildBuilder :: with_capacity ( & DType :: from ( I32 ) , 0 ) ;
505+ builder. set_min_chunk_len ( 0 ) ;
506+
507+ builder. append_array ( & constant ( 1 , 1 ) , & mut ctx) ?;
508+ builder. append_array ( & constant ( 2 , 1 ) , & mut ctx) ?;
509+
510+ let child = builder. finish ( ) ;
511+ let chunked = child. as_ :: < Chunked > ( ) ;
512+ assert_eq ! ( chunked. nchunks( ) , 2 ) ;
513+ assert ! ( chunked. iter_chunks( ) . all( |chunk| chunk. is:: <Constant >( ) ) ) ;
514+
515+ Ok ( ( ) )
516+ }
517+
518+ /// The threshold reaches the children of a child: an array short enough to be materialized
519+ /// lands in the scalar builder, which is itself a nested builder with children of its own.
520+ #[ test]
521+ fn test_min_chunk_len_applies_transitively ( ) -> VortexResult < ( ) > {
522+ let mut ctx = array_session ( ) . create_execution_ctx ( ) ;
523+ let dtype = DType :: List ( Arc :: new ( DType :: from ( I32 ) ) , NonNullable ) ;
524+ let mut builder = ChildBuilder :: with_capacity ( & dtype, 0 ) ;
525+ builder. set_min_chunk_len ( 4 * MIN_CHUNK_LEN ) ;
526+
527+ // Two lists — far too few to be a chunk — holding enough elements between them that those
528+ // elements would become a chunk if the threshold stopped at the outer builder.
529+ let lists = ListViewArray :: new (
530+ constant ( 1 , 2 * MIN_CHUNK_LEN ) ,
531+ buffer ! [ 0u64 , MIN_CHUNK_LEN as u64 ] . into_array ( ) ,
532+ buffer ! [ MIN_CHUNK_LEN as u64 ; 2 ] . into_array ( ) ,
533+ Validity :: NonNullable ,
534+ )
535+ . into_array ( ) ;
536+ builder. append_array ( & lists, & mut ctx) ?;
537+
538+ let child = builder. finish ( ) ;
539+ assert ! ( child. as_:: <ListView >( ) . elements( ) . is:: <Primitive >( ) ) ;
540+
541+ Ok ( ( ) )
542+ }
543+
479544 #[ test]
480545 fn test_finish_resets_the_builder ( ) -> VortexResult < ( ) > {
481546 let mut ctx = array_session ( ) . create_execution_ctx ( ) ;
0 commit comments