Skip to content

Commit f85b81b

Browse files
committed
simplify
Signed-off-by: Matt Katz <mhkatz97@gmail.com>
1 parent 2cb0204 commit f85b81b

1 file changed

Lines changed: 7 additions & 182 deletions

File tree

vortex-layout/src/layouts/list/reader.rs

Lines changed: 7 additions & 182 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ use vortex_array::Canonical;
1212
use vortex_array::IntoArray;
1313
use vortex_array::MaskFuture;
1414
use vortex_array::VortexSessionExecute;
15-
use vortex_array::arrays::ConstantArray;
1615
use vortex_array::arrays::ListArray;
1716
use vortex_array::builtins::ArrayBuiltins;
1817
use vortex_array::dtype::DType;
@@ -23,7 +22,6 @@ use vortex_array::expr::BoundExpression;
2322
use vortex_array::expr::root;
2423
use vortex_array::scalar_fn::fns::operators::Operator;
2524
use vortex_array::validity::Validity;
26-
use vortex_error::VortexExpect;
2725
use vortex_error::VortexResult;
2826
use vortex_mask::Mask;
2927
use vortex_session::VortexSession;
@@ -34,7 +32,6 @@ use crate::LayoutReaderContext;
3432
use crate::LayoutReaderRef;
3533
use crate::RowSplits;
3634
use crate::SplitRange;
37-
use crate::layouts::flat::Flat;
3835
use crate::layouts::list::ListLayout;
3936
use crate::layouts::list::expr::ListChildrenNeeded;
4037
use 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.
4946
const 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)]
5850
pub 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

6859
impl 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-
360259
fn 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).
480365
fn 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.
535386
fn 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

Comments
 (0)