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