1use crate::arrow::array_reader::ArrayReader;
22use crate::arrow::arrow_reader::selection::{
23 LoadedRowRanges, RowSelectionInner, RowSelectionPolicy, RowSelectionStrategy,
24};
25use crate::arrow::arrow_reader::{
26 ArrowPredicate, ParquetRecordBatchReader, RowSelection, RowSelectionCursor, RowSelector,
27};
28use crate::errors::{ParquetError, Result};
29use arrow_array::{Array, BooleanArray};
30use arrow_buffer::{BooleanBuffer, BooleanBufferBuilder};
31use arrow_select::filter::prep_null_mask_filter;
32use std::sync::Arc;
33
34pub struct PredicateOptions<'a> {
36 array_reader: Box<dyn ArrayReader>,
37 predicate: &'a mut dyn ArrowPredicate,
38 limit: Option<usize>,
39 total_rows: usize,
40}
41
42impl<'a> PredicateOptions<'a> {
43 pub fn new(array_reader: Box<dyn ArrayReader>, predicate: &'a mut dyn ArrowPredicate) -> Self {
50 Self {
51 array_reader,
52 predicate,
53 limit: None,
54 total_rows: 0,
55 }
56 }
57
58 pub fn with_limit(mut self, limit: usize, total_rows: usize) -> Self {
74 self.limit = Some(limit);
75 self.total_rows = total_rows;
76 self
77 }
78}
79
80#[derive(Clone, Debug)]
82pub struct ReadPlanBuilder {
83 batch_size: usize,
84 selection: Option<RowSelection>,
86 row_selection_policy: RowSelectionPolicy,
88 loaded_row_ranges: Option<Arc<LoadedRowRanges>>,
90}
91
92impl ReadPlanBuilder {
93 pub fn new(batch_size: usize) -> Self {
95 Self {
96 batch_size,
97 selection: None,
98 row_selection_policy: RowSelectionPolicy::default(),
99 loaded_row_ranges: None,
100 }
101 }
102
103 pub fn with_selection(mut self, selection: Option<RowSelection>) -> Self {
105 self.selection = selection;
106 self
107 }
108
109 pub fn with_row_selection_policy(mut self, policy: RowSelectionPolicy) -> Self {
113 self.row_selection_policy = policy;
114 self
115 }
116
117 pub(crate) fn with_loaded_row_ranges(mut self, ranges: Option<LoadedRowRanges>) -> Self {
118 self.loaded_row_ranges = ranges.map(Arc::new);
119 self
120 }
121
122 pub fn row_selection_policy(&self) -> &RowSelectionPolicy {
124 &self.row_selection_policy
125 }
126
127 pub fn selection(&self) -> Option<&RowSelection> {
129 self.selection.as_ref()
130 }
131
132 pub(crate) fn limited(self, row_count: usize) -> LimitedReadPlanBuilder {
140 LimitedReadPlanBuilder::new(self, row_count)
141 }
142
143 pub fn selects_any(&self) -> bool {
145 self.selection
146 .as_ref()
147 .map(|s| s.selects_any())
148 .unwrap_or(true)
149 }
150
151 pub fn num_rows_selected(&self) -> Option<usize> {
153 self.selection.as_ref().map(|s| s.row_count())
154 }
155
156 pub(crate) fn resolve_selection_strategy(&self) -> RowSelectionStrategy {
160 match self.row_selection_policy {
161 RowSelectionPolicy::Selectors => RowSelectionStrategy::Selectors,
162 RowSelectionPolicy::Mask => RowSelectionStrategy::Mask,
163 RowSelectionPolicy::Auto { threshold, .. } => {
164 let selection = match self.selection.as_ref() {
165 Some(selection) => selection,
166 None => return RowSelectionStrategy::Selectors,
167 };
168
169 selection.auto_selection_strategy(threshold)
170 }
171 }
172 }
173
174 pub fn with_predicate(
184 self,
185 array_reader: Box<dyn ArrayReader>,
186 predicate: &mut dyn ArrowPredicate,
187 ) -> Result<Self> {
188 self.with_predicate_options(PredicateOptions::new(array_reader, predicate))
189 }
190
191 pub fn with_predicate_options(mut self, options: PredicateOptions<'_>) -> Result<Self> {
198 let PredicateOptions {
199 array_reader,
200 predicate,
201 limit,
202 total_rows,
203 } = options;
204
205 let expected_rows = match self.selection.as_ref() {
212 Some(s) => Some(s.row_count()),
213 None => limit.map(|_| total_rows),
214 };
215
216 let reader = ParquetRecordBatchReader::new(array_reader, self.clone().build());
217 let mut filters = vec![];
218 let mut processed_rows: usize = 0;
219 let mut matched_rows: usize = 0;
220 for maybe_batch in reader {
221 let maybe_batch = maybe_batch?;
222 let input_rows = maybe_batch.num_rows();
223 let filter = predicate.evaluate(maybe_batch)?;
224 if filter.len() != input_rows {
226 return Err(arrow_err!(
227 "ArrowPredicate predicate returned {} rows, expected {input_rows}",
228 filter.len()
229 ));
230 }
231 let filter = match filter.null_count() {
232 0 => filter,
233 _ => prep_null_mask_filter(&filter),
236 };
237
238 processed_rows += input_rows;
239
240 match limit {
241 Some(limit) if limit - matched_rows <= filter.len() => {
242 let truncated = filter.take_n_true(limit - matched_rows);
243 matched_rows += truncated.true_count();
244 filters.push(truncated);
245 if matched_rows >= limit {
246 break;
247 }
248 }
249 _ => {
250 matched_rows += filter.true_count();
251 filters.push(filter);
252 }
253 }
254 }
255
256 if let Some(expected) = expected_rows
262 && processed_rows < expected
263 {
264 let pad_len = expected - processed_rows;
265 filters.push(BooleanArray::new(BooleanBuffer::new_unset(pad_len), None));
266 }
267
268 let all_selected = filters.iter().all(|f| f.true_count() == f.len());
273 if all_selected {
274 return Ok(self);
275 }
276 let raw = if self
277 .selection
278 .as_ref()
279 .is_some_and(|s| s.as_mask().is_some())
280 {
281 RowSelection::from_boolean_buffer(filters_to_boolean_buffer(&filters))
282 } else {
283 RowSelection::from_filters(&filters)
284 };
285 self.selection = match self.selection.take() {
286 Some(selection) => Some(selection.and_then(&raw)),
287 None => Some(raw),
288 };
289 Ok(self)
290 }
291
292 pub fn build(mut self) -> ReadPlan {
294 if !self.selects_any() {
296 self.selection = Some(RowSelection::from(vec![]));
297 }
298
299 let selection_strategy = self.resolve_selection_strategy();
301
302 let Self {
303 batch_size,
304 selection,
305 row_selection_policy: _,
306 loaded_row_ranges,
307 } = self;
308
309 let row_selection_cursor = selection
310 .map(|s| build_cursor(s.trim(), selection_strategy, loaded_row_ranges))
311 .unwrap_or(RowSelectionCursor::new_all());
312
313 ReadPlan {
314 batch_size,
315 row_selection_cursor,
316 }
317 }
318}
319
320fn build_cursor(
322 selection: RowSelection,
323 strategy: RowSelectionStrategy,
324 loaded_row_ranges: Option<Arc<LoadedRowRanges>>,
325) -> RowSelectionCursor {
326 match (strategy, selection.into_inner()) {
327 (RowSelectionStrategy::Mask, RowSelectionInner::Mask(mask)) => {
328 RowSelectionCursor::new_mask_from_buffer((*mask).into_mask(), loaded_row_ranges)
329 }
330 (RowSelectionStrategy::Mask, RowSelectionInner::Selectors(selectors)) => {
331 RowSelectionCursor::new_mask_from_selectors(selectors, loaded_row_ranges)
332 }
333 (RowSelectionStrategy::Selectors, RowSelectionInner::Selectors(selectors)) => {
334 RowSelectionCursor::new_selectors(selectors)
335 }
336 (RowSelectionStrategy::Selectors, RowSelectionInner::Mask(mask)) => {
337 RowSelectionCursor::new_selectors((*mask).into_selectors())
338 }
339 }
340}
341
342pub(crate) struct LimitedReadPlanBuilder {
346 inner: ReadPlanBuilder,
348 row_count: usize,
351 offset: Option<usize>,
353 limit: Option<usize>,
355}
356
357impl LimitedReadPlanBuilder {
358 fn new(inner: ReadPlanBuilder, row_count: usize) -> Self {
360 Self {
361 inner,
362 row_count,
363 offset: None,
364 limit: None,
365 }
366 }
367
368 pub(crate) fn with_offset(mut self, offset: Option<usize>) -> Self {
370 self.offset = offset;
371 self
372 }
373
374 pub(crate) fn with_limit(mut self, limit: Option<usize>) -> Self {
376 self.limit = limit;
377 self
378 }
379
380 pub(crate) fn build_limited(self) -> ReadPlanBuilder {
383 let Self {
384 mut inner,
385 row_count,
386 offset,
387 limit,
388 } = self;
389
390 if !inner.selects_any() {
392 inner.selection = Some(RowSelection::from(vec![]));
393 }
394
395 if let Some(offset) = offset {
397 inner.selection = Some(match row_count.checked_sub(offset) {
398 None => RowSelection::from(vec![]),
399 Some(remaining) => inner
400 .selection
401 .map(|selection| selection.offset(offset))
402 .unwrap_or_else(|| {
403 RowSelection::from(vec![
404 RowSelector::skip(offset),
405 RowSelector::select(remaining),
406 ])
407 }),
408 });
409 }
410
411 if let Some(limit) = limit {
413 inner.selection = Some(
414 inner
415 .selection
416 .map(|selection| selection.limit(limit))
417 .unwrap_or_else(|| {
418 RowSelection::from(vec![RowSelector::select(limit.min(row_count))])
419 }),
420 );
421 }
422
423 inner
424 }
425}
426
427fn filters_to_boolean_buffer(filters: &[BooleanArray]) -> BooleanBuffer {
428 let total_rows = filters.iter().map(|f| f.len()).sum();
429 let mut builder = BooleanBufferBuilder::new(total_rows);
430 for filter in filters {
431 assert_eq!(filter.null_count(), 0);
432 builder.append_buffer(filter.values());
433 }
434 builder.finish()
435}
436
437#[derive(Debug)]
441pub struct ReadPlan {
442 batch_size: usize,
444 row_selection_cursor: RowSelectionCursor,
446}
447
448impl ReadPlan {
449 pub fn row_selection_cursor_mut(&mut self) -> &mut RowSelectionCursor {
451 &mut self.row_selection_cursor
452 }
453
454 #[inline(always)]
456 pub fn batch_size(&self) -> usize {
457 self.batch_size
458 }
459}
460
461#[cfg(test)]
462mod tests {
463 use super::*;
464
465 fn builder_with_selection(selection: RowSelection) -> ReadPlanBuilder {
466 ReadPlanBuilder::new(1024).with_selection(Some(selection))
467 }
468
469 #[test]
470 fn preferred_selection_strategy_prefers_mask_by_default() {
471 let selection = RowSelection::from(vec![RowSelector::select(8)]);
472 let builder = builder_with_selection(selection);
473 assert_eq!(
474 builder.resolve_selection_strategy(),
475 RowSelectionStrategy::Mask
476 );
477 }
478
479 #[test]
480 fn preferred_selection_strategy_prefers_selectors_when_threshold_small() {
481 let selection = RowSelection::from(vec![RowSelector::select(8)]);
482 let builder = builder_with_selection(selection)
483 .with_row_selection_policy(RowSelectionPolicy::Auto { threshold: 1 });
484 assert_eq!(
485 builder.resolve_selection_strategy(),
486 RowSelectionStrategy::Selectors
487 );
488 }
489
490 #[test]
491 fn preferred_selection_strategy_handles_dense_mask_backing() {
492 let bits: Vec<_> = (0..16).map(|i| i % 2 == 0).collect();
493 let selection = RowSelection::from_boolean_buffer(BooleanBuffer::from(bits));
494 let builder = builder_with_selection(selection)
495 .with_row_selection_policy(RowSelectionPolicy::Auto { threshold: 4 });
496 assert_eq!(
497 builder.resolve_selection_strategy(),
498 RowSelectionStrategy::Mask
499 );
500 }
501
502 #[test]
503 fn preferred_selection_strategy_handles_sparse_mask_backing() {
504 let bits: Vec<_> = (0..128).map(|i| i < 64).collect();
505 let selection = RowSelection::from_boolean_buffer(BooleanBuffer::from(bits));
506 let builder = builder_with_selection(selection)
507 .with_row_selection_policy(RowSelectionPolicy::Auto { threshold: 32 });
508 assert_eq!(
509 builder.resolve_selection_strategy(),
510 RowSelectionStrategy::Selectors
511 );
512 }
513
514 #[test]
515 fn preferred_selection_strategy_preserves_mask_threshold_boundaries() {
516 let mask = BooleanBuffer::from(vec![true; 8]);
517
518 let empty = builder_with_selection(RowSelection::from_boolean_buffer(
519 BooleanBuffer::new_unset(0),
520 ));
521 assert_eq!(
522 empty.resolve_selection_strategy(),
523 RowSelectionStrategy::Mask
524 );
525
526 let disabled = builder_with_selection(RowSelection::from_boolean_buffer(mask.clone()))
527 .with_row_selection_policy(RowSelectionPolicy::Auto { threshold: 0 });
528 assert_eq!(
529 disabled.resolve_selection_strategy(),
530 RowSelectionStrategy::Selectors
531 );
532
533 let equal = builder_with_selection(RowSelection::from_boolean_buffer(mask.clone()))
535 .with_row_selection_policy(RowSelectionPolicy::Auto { threshold: 8 });
536 assert_eq!(
537 equal.resolve_selection_strategy(),
538 RowSelectionStrategy::Selectors
539 );
540
541 let above = builder_with_selection(RowSelection::from_boolean_buffer(mask))
542 .with_row_selection_policy(RowSelectionPolicy::Auto { threshold: 9 });
543 assert_eq!(
544 above.resolve_selection_strategy(),
545 RowSelectionStrategy::Mask
546 );
547 }
548
549 #[test]
550 fn preferred_selection_strategy_mask_matches_selector_backing() {
551 use rand::{RngExt, rng};
552
553 let mut rand = rng();
554 for _ in 0..200 {
555 let len = rand.random_range(0..256);
556 let bits: Vec<_> = (0..len).map(|_| rand.random_bool(0.5)).collect();
557 let mask_backed = RowSelection::from_boolean_buffer(BooleanBuffer::from(bits.clone()));
558 let selector_backed = RowSelection::from_filters(&[BooleanArray::from(bits)]);
559
560 for threshold in [0, 1, 2, 8, 32, 64, usize::MAX] {
561 assert_eq!(
562 mask_backed.auto_selection_strategy(threshold),
563 selector_backed.auto_selection_strategy(threshold),
564 "strategy differs for len {len} and threshold {threshold}"
565 );
566 }
567 }
568 }
569
570 #[test]
571 fn mask_plan_trims_trailing_skips_before_chunking() {
572 let mut plan = ReadPlanBuilder::new(8)
573 .with_selection(Some(RowSelection::from(vec![
574 RowSelector::select(1),
575 RowSelector::skip(7),
576 ])))
577 .with_row_selection_policy(RowSelectionPolicy::Mask)
578 .build();
579 let RowSelectionCursor::Mask(cursor) = plan.row_selection_cursor_mut() else {
580 panic!("expected a Mask cursor");
581 };
582
583 let chunk = cursor.next_chunk(8).unwrap();
584 assert_eq!(chunk.chunk_rows, 1);
585 assert_eq!(chunk.selected_rows, 1);
586 assert!(cursor.is_empty());
587 }
588
589 #[test]
590 fn selectors_policy_lowers_mask_backed_selection() {
591 let selection = RowSelection::from_boolean_buffer(BooleanBuffer::from(vec![
592 true, false, false, true, true,
593 ]));
594 let mut plan = ReadPlanBuilder::new(4)
595 .with_selection(Some(selection))
596 .with_row_selection_policy(RowSelectionPolicy::Selectors)
597 .build();
598 let RowSelectionCursor::Selectors(cursor) = plan.row_selection_cursor_mut() else {
599 panic!("expected a Selectors cursor");
600 };
601
602 assert_eq!(cursor.next_selector(), RowSelector::select(1));
603 assert_eq!(cursor.next_selector(), RowSelector::skip(2));
604 assert_eq!(cursor.next_selector(), RowSelector::select(2));
605 assert!(cursor.is_empty());
606 }
607
608 #[test]
609 fn mask_backed_plan_respects_loaded_row_ranges() {
610 let mut bits = vec![false; 5];
614 bits.push(true); bits.extend(std::iter::repeat_n(false, 10)); bits.push(true); bits.extend([true, false, true]); let mask = BooleanBuffer::from(bits).slice(5, 12);
619 let selection = RowSelection::from_boolean_buffer(mask);
620 assert!(selection.as_mask().is_some());
621
622 let loaded = LoadedRowRanges::from_selection(RowSelection::from(vec![
623 RowSelector::select(4),
624 RowSelector::skip(6),
625 RowSelector::select(2),
626 ]));
627
628 let mut plan = ReadPlanBuilder::new(12)
629 .with_selection(Some(selection))
630 .with_row_selection_policy(RowSelectionPolicy::Mask)
631 .with_loaded_row_ranges(Some(loaded))
632 .build();
633 let RowSelectionCursor::Mask(cursor) = plan.row_selection_cursor_mut() else {
634 panic!("expected a Mask cursor");
635 };
636
637 let first = cursor.next_chunk(12).unwrap();
640 assert_eq!(first.initial_skip, 0);
641 assert_eq!(first.chunk_rows, 4);
642 assert_eq!(first.selected_rows, 1);
643
644 let second = cursor.next_chunk(12).unwrap();
646 assert_eq!(second.initial_skip, 7);
647 assert_eq!(second.chunk_rows, 1);
648 assert_eq!(second.selected_rows, 1);
649 assert!(cursor.is_empty());
650 }
651
652 #[test]
653 fn with_predicate_options_limit_pads_tail_when_no_prior_selection() {
654 use crate::arrow::ProjectionMask;
655 use crate::arrow::array_reader::StructArrayReader;
656 use crate::arrow::array_reader::test_util::make_int32_page_reader;
657 use crate::arrow::arrow_reader::ArrowPredicateFn;
658 use arrow_schema::{DataType as ArrowType, Field, Fields};
659
660 const TOTAL_ROWS: usize = 100;
665 const LIMIT: usize = 10;
666
667 let data: Vec<i32> = (0..TOTAL_ROWS as i32).collect();
668 let levels = vec![0; TOTAL_ROWS];
669 let leaf = make_int32_page_reader(&data, &levels, &levels, 0, 0, None);
670 let struct_type = ArrowType::Struct(Fields::from(vec![Field::new(
671 "c0",
672 ArrowType::Int32,
673 false,
674 )]));
675 let struct_reader = StructArrayReader::new(struct_type, vec![leaf], 0, 0, false, None);
676
677 let mut predicate = ArrowPredicateFn::new(ProjectionMask::all(), |batch| {
678 Ok(BooleanArray::from(vec![true; batch.num_rows()]))
679 });
680
681 let builder = ReadPlanBuilder::new(16)
682 .with_predicate_options(
683 PredicateOptions::new(Box::new(struct_reader), &mut predicate)
684 .with_limit(LIMIT, TOTAL_ROWS),
685 )
686 .unwrap();
687
688 let selection = builder
689 .selection()
690 .expect("limit-driven early break must produce a selection");
691
692 assert_eq!(selection.row_count(), LIMIT);
694
695 let total: usize = selection.iter().map(|s| s.row_count).sum();
698 assert_eq!(
699 total, TOTAL_ROWS,
700 "selection must span the full row group, not only the prefix evaluated before the limit"
701 );
702 }
703
704 #[test]
705 fn with_predicate_options_preserves_mask_selection() {
706 use crate::arrow::ProjectionMask;
707 use crate::arrow::array_reader::StructArrayReader;
708 use crate::arrow::array_reader::test_util::make_int32_page_reader;
709 use crate::arrow::arrow_reader::ArrowPredicateFn;
710 use arrow_schema::{DataType as ArrowType, Field, Fields};
711
712 let data: Vec<i32> = (0..6).collect();
713 let levels = vec![0; data.len()];
714 let leaf = make_int32_page_reader(&data, &levels, &levels, 0, 0, None);
715 let struct_type = ArrowType::Struct(Fields::from(vec![Field::new(
716 "c0",
717 ArrowType::Int32,
718 false,
719 )]));
720 let struct_reader = StructArrayReader::new(struct_type, vec![leaf], 0, 0, false, None);
721
722 let prior = RowSelection::from_boolean_buffer(BooleanBuffer::from(vec![
723 true, false, true, true, false, true,
724 ]));
725 let mut filters = vec![BooleanArray::from(vec![true, false, true, false])];
726 let mut predicate = ArrowPredicateFn::new(ProjectionMask::all(), move |batch| {
727 assert_eq!(batch.num_rows(), 4);
728 Ok(filters.remove(0))
729 });
730
731 let builder = ReadPlanBuilder::new(16)
732 .with_selection(Some(prior))
733 .with_predicate_options(PredicateOptions::new(
734 Box::new(struct_reader),
735 &mut predicate,
736 ))
737 .unwrap();
738
739 let selection = builder.selection().unwrap();
740 assert!(selection.as_mask().is_some());
741
742 let expected = RowSelection::from_boolean_buffer(BooleanBuffer::from(vec![
743 true, false, false, true, false, false,
744 ]));
745 assert_eq!(selection, &expected);
746 }
747
748 #[test]
749 fn with_predicate_options_limit_handles_null_filters() {
750 use crate::arrow::ProjectionMask;
751 use crate::arrow::array_reader::StructArrayReader;
752 use crate::arrow::array_reader::test_util::make_int32_page_reader;
753 use crate::arrow::arrow_reader::ArrowPredicateFn;
754 use arrow_schema::{DataType as ArrowType, Field, Fields};
755
756 const TOTAL_ROWS: usize = 100;
757 const LIMIT: usize = 10;
758
759 let data: Vec<i32> = (0..TOTAL_ROWS as i32).collect();
760 let levels = vec![0; TOTAL_ROWS];
761 let leaf = make_int32_page_reader(&data, &levels, &levels, 0, 0, None);
762 let struct_type = ArrowType::Struct(Fields::from(vec![Field::new(
763 "c0",
764 ArrowType::Int32,
765 false,
766 )]));
767 let struct_reader = StructArrayReader::new(struct_type, vec![leaf], 0, 0, false, None);
768
769 let mut predicate = ArrowPredicateFn::new(ProjectionMask::all(), |batch| {
770 Ok((0..batch.num_rows())
771 .map(|i| match i % 4 {
772 0 | 2 => Some(true),
773 1 => None,
774 _ => Some(false),
775 })
776 .collect::<BooleanArray>())
777 });
778
779 let builder = ReadPlanBuilder::new(16)
780 .with_predicate_options(
781 PredicateOptions::new(Box::new(struct_reader), &mut predicate)
782 .with_limit(LIMIT, TOTAL_ROWS),
783 )
784 .unwrap();
785
786 let selection = builder
787 .selection()
788 .expect("limit-driven early break must produce a selection");
789
790 assert_eq!(selection.row_count(), LIMIT);
791
792 let total: usize = selection.iter().map(|s| s.row_count).sum();
793 assert_eq!(total, TOTAL_ROWS);
794 }
795}