@@ -12,7 +12,6 @@ use vortex_array::Canonical;
1212use vortex_array:: IntoArray ;
1313use vortex_array:: MaskFuture ;
1414use vortex_array:: VortexSessionExecute ;
15- use vortex_array:: arrays:: ConstantArray ;
1615use vortex_array:: arrays:: ListArray ;
1716use vortex_array:: builtins:: ArrayBuiltins ;
1817use vortex_array:: dtype:: DType ;
@@ -23,7 +22,6 @@ use vortex_array::expr::BoundExpression;
2322use vortex_array:: expr:: root;
2423use vortex_array:: scalar_fn:: fns:: operators:: Operator ;
2524use vortex_array:: validity:: Validity ;
26- use vortex_error:: VortexExpect ;
2725use vortex_error:: VortexResult ;
2826use vortex_mask:: Mask ;
2927use vortex_session:: VortexSession ;
@@ -34,7 +32,6 @@ use crate::LayoutReaderContext;
3432use crate :: LayoutReaderRef ;
3533use crate :: RowSplits ;
3634use crate :: SplitRange ;
37- use crate :: layouts:: flat:: Flat ;
3835use crate :: layouts:: list:: ListLayout ;
3936use crate :: layouts:: list:: expr:: ListChildrenNeeded ;
4037use crate :: layouts:: list:: expr:: get_necessary_bound_list_children;
@@ -48,11 +45,6 @@ type OptionalArrayFuture = BoxFuture<'static, VortexResult<Option<ArrayRef>>>;
4845/// and above which we evaluate the expression over all rows and intersect afterward.
4946const EXPR_EVAL_THRESHOLD : f64 = 0.2 ;
5047
51- /// Above this average element count, selective projections use a bounded elements read. Rebuilding
52- /// a complete page is important for preserving nested encodings on ordinary lists, but is wasteful
53- /// for columns such as genotypes with thousands of elements per outer row.
54- const COMPLETE_PAGE_MAX_AVERAGE_LIST_LENGTH : u64 = 1024 ;
55-
5648/// Reader for [`ListLayout`].
5749#[ derive( Clone ) ]
5850pub struct ListReader {
@@ -62,7 +54,6 @@ pub struct ListReader {
6254 elements : LayoutReaderRef ,
6355 offsets : LayoutReaderRef ,
6456 validity : Option < LayoutReaderRef > ,
65- children_are_flat : bool ,
6657}
6758
6859impl ListReader {
@@ -76,11 +67,6 @@ impl ListReader {
7667 let elements_layout = layout. elements ( ) ?;
7768 let offsets_layout = layout. offsets ( ) ?;
7869 let validity_layout = layout. validity ( ) ?;
79- let children_are_flat = elements_layout. is :: < Flat > ( )
80- && offsets_layout. is :: < Flat > ( )
81- && validity_layout
82- . as_ref ( )
83- . is_none_or ( |layout| layout. is :: < Flat > ( ) ) ;
8470 let elements = elements_layout. new_reader (
8571 format ! ( "{name}.elements" ) . into ( ) ,
8672 Arc :: clone ( & segment_source) ,
@@ -111,7 +97,6 @@ impl ListReader {
11197 elements,
11298 offsets,
11399 validity,
114- children_are_flat,
115100 } )
116101 }
117102
@@ -156,44 +141,13 @@ impl ListReader {
156141 . boxed ( ) )
157142 }
158143
159- /// Projection for [`ListChildrenNeeded::All`] expressions.
160- ///
161- /// Flat children are fetched in full and then sliced/filtered in outer-row space, matching the
162- /// operation order of a flat list page. Legacy layouts with non-flat children retain their
163- /// bounded child-read path.
144+ /// Projection for [`ListChildrenNeeded::All`] expressions. Registers complete child reads
145+ /// eagerly and reconstructs the list page before applying the outer-row mask.
164146 fn project_all (
165147 & self ,
166148 row_range : & Range < u64 > ,
167149 expr : & BoundExpression ,
168150 mask : MaskFuture ,
169- ) -> VortexResult < ArrayFuture > {
170- let is_full_range = row_range. start == 0 && row_range. end == self . layout . row_count ( ) ;
171- let reader = self . clone ( ) ;
172- let row_range = row_range. clone ( ) ;
173- let expr = expr. clone ( ) ;
174- Ok ( async move {
175- let mask = mask. await ?;
176- if should_read_complete_page (
177- reader. children_are_flat ,
178- is_full_range,
179- mask. all_true ( ) ,
180- reader. layout . row_count ( ) ,
181- reader. elements . row_count ( ) ,
182- ) {
183- reader. project_all_complete ( & row_range, & expr, mask) ?. await
184- } else {
185- reader. project_all_bounded ( & row_range, & expr, mask) ?. await
186- }
187- }
188- . boxed ( ) )
189- }
190-
191- /// Fetch the complete `elements`, `offsets`, and `validity` children concurrently.
192- fn project_all_complete (
193- & self ,
194- row_range : & Range < u64 > ,
195- expr : & BoundExpression ,
196- mask : Mask ,
197151 ) -> VortexResult < ArrayFuture > {
198152 let row_count = self . layout . row_count ( ) ;
199153 let elements_row_count = self . elements . row_count ( ) ;
@@ -210,6 +164,11 @@ impl ListReader {
210164 ) ?;
211165
212166 Ok ( async move {
167+ let mask = mask. await ?;
168+ if mask. all_false ( ) {
169+ return Ok ( Canonical :: empty ( expr. dtype ( ) ) . into_array ( ) ) ;
170+ }
171+
213172 let ( offsets, elements, validity) = try_join ! ( offsets_fut, elements_fut, validity_fut) ?;
214173 // SAFETY: ListLayout is constructed from a valid ListArray and reading its children
215174 // without transformation preserves the list invariants.
@@ -231,62 +190,6 @@ impl ListReader {
231190 . boxed ( ) )
232191 }
233192
234- /// Bounded read for a sub-range or selective mask.
235- ///
236- /// Crops leading and trailing unselected lists, reads their offsets, and translates the first
237- /// and last offset into the element-row range to fetch. Any holes in the selection are filtered
238- /// after reconstructing the list array.
239- fn project_all_bounded (
240- & self ,
241- row_range : & Range < u64 > ,
242- expr : & BoundExpression ,
243- mask : Mask ,
244- ) -> VortexResult < ArrayFuture > {
245- // Crop to the smallest contiguous row range containing every selected list.
246- let Some ( selected_rows) = selected_row_range ( & mask) else {
247- let empty = Canonical :: empty ( expr. dtype ( ) ) . into_array ( ) ;
248- return Ok ( async move { Ok ( empty) } . boxed ( ) ) ;
249- } ;
250-
251- let selected_mask = mask. slice ( selected_rows. clone ( ) ) ;
252- let selected_row_range = ( row_range. start + u64:: try_from ( selected_rows. start ) ?)
253- ..( row_range. start + u64:: try_from ( selected_rows. end ) ?) ;
254-
255- let nullability = self . layout . dtype ( ) . nullability ( ) ;
256- let expr = expr. clone ( ) ;
257- let reader = self . clone ( ) ;
258- let offsets_fut = self . fetch_raw_offsets ( & selected_row_range) ?;
259-
260- Ok ( async move {
261- let offsets = offsets_fut. await ?;
262-
263- let elements_range = elements_range_from_offsets ( & offsets, & reader. session ) ?;
264- let elements_fut = reader. fetch_raw_elements ( & elements_range) ?;
265- let validity_fut = fetch_validity (
266- reader. validity . as_ref ( ) ,
267- & selected_row_range,
268- MaskFuture :: new_true ( selected_mask. len ( ) ) ,
269- ) ?;
270- let ( elements, validity) = try_join ! ( elements_fut, validity_fut) ?;
271-
272- let offsets = rebase_offsets ( offsets, elements_range. start ) ?;
273- // SAFETY: the selected offsets remain monotonically increasing, rebasing them against
274- // the selected element range preserves their lengths, and validity covers the same
275- // cropped list rows.
276- let list = unsafe {
277- ListArray :: new_unchecked ( elements, offsets, create_validity ( validity, nullability) )
278- }
279- . into_array ( ) ;
280- let list = if selected_mask. all_true ( ) {
281- list
282- } else {
283- list. filter ( selected_mask) ?
284- } ;
285- list. apply_bound ( & expr)
286- }
287- . boxed ( ) )
288- }
289-
290193 /// Projection for [`ListChildrenNeeded::OffsetsAndValidity`] expressions. Only reads offsets and validity children.
291194 fn project_offsets_validity (
292195 & self ,
@@ -353,10 +256,6 @@ impl ListReader {
353256 }
354257}
355258
356- fn selected_row_range ( mask : & Mask ) -> Option < Range < usize > > {
357- Some ( mask. first ( ) ?..mask. last ( ) ? + 1 )
358- }
359-
360259fn create_validity ( validity_array : Option < ArrayRef > , nullability : Nullability ) -> Validity {
361260 match validity_array {
362261 Some ( arr) => Validity :: Array ( arr) ,
@@ -461,20 +360,6 @@ impl LayoutReader for ListReader {
461360 }
462361}
463362
464- fn should_read_complete_page (
465- children_are_flat : bool ,
466- is_full_range : bool ,
467- mask_all_true : bool ,
468- row_count : u64 ,
469- elements_row_count : u64 ,
470- ) -> bool {
471- if is_full_range && mask_all_true {
472- return true ;
473- }
474- children_are_flat
475- && elements_row_count <= row_count. saturating_mul ( COMPLETE_PAGE_MAX_AVERAGE_LIST_LENGTH )
476- }
477-
478363/// Fetch the validity child for `row_range` under `mask`, yielding `None` for a non-nullable list
479364/// (which has no validity child).
480365fn fetch_validity (
@@ -497,40 +382,6 @@ fn fetch_validity(
497382 . boxed ( ) )
498383}
499384
500- /// Read `offsets[0]` and `offsets[-1]` and return the elements range they bound.
501- fn elements_range_from_offsets (
502- offsets : & ArrayRef ,
503- session : & VortexSession ,
504- ) -> VortexResult < Range < u64 > > {
505- if offsets. is_empty ( ) {
506- return Ok ( 0 ..0 ) ;
507- }
508- let mut exec_ctx = session. create_execution_ctx ( ) ;
509- let start = offsets
510- . execute_scalar ( 0 , & mut exec_ctx) ?
511- . as_primitive ( )
512- . as_ :: < u64 > ( )
513- . vortex_expect ( "offset value fits in u64" ) ;
514- let end = offsets
515- . execute_scalar ( offsets. len ( ) - 1 , & mut exec_ctx) ?
516- . as_primitive ( )
517- . as_ :: < u64 > ( )
518- . vortex_expect ( "offset value fits in u64" ) ;
519- Ok ( start..end)
520- }
521-
522- /// Subtract `first` from every offset so they index into a sliced `elements[first..]` buffer that
523- /// starts at zero.
524- fn rebase_offsets ( offsets : ArrayRef , first : u64 ) -> VortexResult < ArrayRef > {
525- if first == 0 {
526- return Ok ( offsets) ;
527- }
528- let constant = ConstantArray :: new ( first, offsets. len ( ) )
529- . into_array ( )
530- . cast ( offsets. dtype ( ) . clone ( ) ) ?;
531- offsets. binary ( constant, Operator :: Sub )
532- }
533-
534385/// Compute `offsets[i + 1] - offsets[i]` as the unmasked list length values.
535386fn list_lengths_from_offsets ( offsets : ArrayRef ) -> VortexResult < ArrayRef > {
536387 let len = offsets. len ( ) . saturating_sub ( 1 ) ;
@@ -597,32 +448,6 @@ mod tests {
597448 use crate :: session:: LayoutSession ;
598449 use crate :: test:: SESSION ;
599450
600- #[ rstest]
601- #[ case:: modest_selective( true , false , false , 100 , 102_400 , true ) ]
602- #[ case:: large_selective( true , false , false , 100 , 102_401 , false ) ]
603- #[ case:: large_partial_all_true( true , false , true , 100 , 102_401 , false ) ]
604- #[ case:: large_complete( true , true , true , 100 , 102_401 , true ) ]
605- #[ case:: legacy_non_flat( false , false , false , 100 , 100 , false ) ]
606- fn complete_page_read_selection (
607- #[ case] children_are_flat : bool ,
608- #[ case] is_full_range : bool ,
609- #[ case] mask_all_true : bool ,
610- #[ case] row_count : u64 ,
611- #[ case] elements_row_count : u64 ,
612- #[ case] expected : bool ,
613- ) {
614- assert_eq ! (
615- should_read_complete_page(
616- children_are_flat,
617- is_full_range,
618- mask_all_true,
619- row_count,
620- elements_row_count,
621- ) ,
622- expected
623- ) ;
624- }
625-
626451 /// Validity-class projections (`is_null` / `is_not_null` of the list) round-trip through the
627452 /// validity-only read path, for both nullable and non-nullable lists.
628453 #[ rstest]
0 commit comments