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;
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 = match (self.selection.as_ref(), self.row_selection_policy) {
276 (Some(selection), _) if selection.as_mask().is_some() => {
277 RowSelection::from_filters_mask(&filters)
278 }
279 (None, RowSelectionPolicy::Auto { threshold }) => {
280 RowSelection::from_filters_auto(&filters, threshold)
281 }
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
426#[derive(Debug)]
430pub struct ReadPlan {
431 batch_size: usize,
433 row_selection_cursor: RowSelectionCursor,
435}
436
437impl ReadPlan {
438 pub fn row_selection_cursor_mut(&mut self) -> &mut RowSelectionCursor {
440 &mut self.row_selection_cursor
441 }
442
443 #[inline(always)]
445 pub fn batch_size(&self) -> usize {
446 self.batch_size
447 }
448}
449
450#[cfg(test)]
451mod tests {
452 use super::*;
453
454 const DEFAULT_AUTO_THRESHOLD: usize = 32;
455
456 fn builder_with_selection(selection: RowSelection) -> ReadPlanBuilder {
457 ReadPlanBuilder::new(1024).with_selection(Some(selection))
458 }
459
460 fn predicate_plan(
461 pattern: Vec<bool>,
462 batch_size: usize,
463 limit: Option<usize>,
464 ) -> ReadPlanBuilder {
465 use crate::arrow::ProjectionMask;
466 use crate::arrow::array_reader::StructArrayReader;
467 use crate::arrow::array_reader::test_util::make_int32_page_reader;
468 use crate::arrow::arrow_reader::ArrowPredicateFn;
469 use arrow_schema::{DataType as ArrowType, Field, Fields};
470
471 let total_rows = pattern.len();
472 let data: Vec<i32> = (0..total_rows as i32).collect();
473 let levels = vec![0; total_rows];
474 let leaf = make_int32_page_reader(&data, &levels, &levels, 0, 0, None);
475 let struct_type = ArrowType::Struct(Fields::from(vec![Field::new(
476 "c0",
477 ArrowType::Int32,
478 false,
479 )]));
480 let struct_reader = StructArrayReader::new(struct_type, vec![leaf], 0, 0, false, None);
481
482 let mut offset = 0usize;
483 let mut predicate = ArrowPredicateFn::new(ProjectionMask::all(), move |batch| {
484 let end = offset + batch.num_rows();
485 let filter = BooleanArray::from(pattern[offset..end].to_vec());
486 offset = end;
487 Ok(filter)
488 });
489 let options = PredicateOptions::new(Box::new(struct_reader), &mut predicate);
490 let options = match limit {
491 Some(limit) => options.with_limit(limit, total_rows),
492 None => options,
493 };
494
495 ReadPlanBuilder::new(batch_size)
496 .with_predicate_options(options)
497 .unwrap()
498 }
499
500 fn first_n_matches(pattern: &[bool], limit: usize) -> Vec<bool> {
501 let mut remaining = limit;
502 pattern
503 .iter()
504 .map(|selected| {
505 if *selected && remaining != 0 {
506 remaining -= 1;
507 true
508 } else {
509 false
510 }
511 })
512 .collect()
513 }
514
515 fn assert_limit_case(name: &str, pattern: Vec<bool>, batch_size: usize, limit: usize) {
516 let expected_bits = first_n_matches(&pattern, limit);
517 let expected = RowSelection::from_filters(&[BooleanArray::from(expected_bits)]);
518 let builder = predicate_plan(pattern, batch_size, Some(limit));
519 let actual = builder
520 .selection()
521 .unwrap_or_else(|| panic!("{name}: limited mixed predicate must produce a selection"));
522
523 assert_eq!(actual, &expected, "{name}: logical selection");
524
525 let current_strategy = expected.auto_selection_strategy(DEFAULT_AUTO_THRESHOLD);
526 assert_eq!(
527 builder.resolve_selection_strategy(),
528 current_strategy,
529 "{name}: Auto strategy"
530 );
531 assert_eq!(
532 actual.as_mask().is_some(),
533 current_strategy == RowSelectionStrategy::Mask,
534 "{name}: backing selected by capped Auto"
535 );
536 }
537
538 #[test]
539 fn preferred_selection_strategy_prefers_mask_by_default() {
540 let selection = RowSelection::from(vec![RowSelector::select(8)]);
541 let builder = builder_with_selection(selection);
542 assert_eq!(
543 builder.resolve_selection_strategy(),
544 RowSelectionStrategy::Mask
545 );
546 }
547
548 #[test]
549 fn preferred_selection_strategy_prefers_selectors_when_threshold_small() {
550 let selection = RowSelection::from(vec![RowSelector::select(8)]);
551 let builder = builder_with_selection(selection)
552 .with_row_selection_policy(RowSelectionPolicy::Auto { threshold: 1 });
553 assert_eq!(
554 builder.resolve_selection_strategy(),
555 RowSelectionStrategy::Selectors
556 );
557 }
558
559 #[test]
560 fn preferred_selection_strategy_handles_dense_mask_backing() {
561 let bits: Vec<_> = (0..16).map(|i| i % 2 == 0).collect();
562 let selection = RowSelection::from_boolean_buffer(BooleanBuffer::from(bits));
563 let builder = builder_with_selection(selection)
564 .with_row_selection_policy(RowSelectionPolicy::Auto { threshold: 4 });
565 assert_eq!(
566 builder.resolve_selection_strategy(),
567 RowSelectionStrategy::Mask
568 );
569 }
570
571 #[test]
572 fn preferred_selection_strategy_handles_sparse_mask_backing() {
573 let bits: Vec<_> = (0..128).map(|i| i < 64).collect();
574 let selection = RowSelection::from_boolean_buffer(BooleanBuffer::from(bits));
575 let builder = builder_with_selection(selection)
576 .with_row_selection_policy(RowSelectionPolicy::Auto { threshold: 32 });
577 assert_eq!(
578 builder.resolve_selection_strategy(),
579 RowSelectionStrategy::Selectors
580 );
581 }
582
583 #[test]
584 fn preferred_selection_strategy_preserves_mask_threshold_boundaries() {
585 let mask = BooleanBuffer::from(vec![true; 8]);
586
587 let empty = builder_with_selection(RowSelection::from_boolean_buffer(
588 BooleanBuffer::new_unset(0),
589 ));
590 assert_eq!(
591 empty.resolve_selection_strategy(),
592 RowSelectionStrategy::Mask
593 );
594
595 let disabled = builder_with_selection(RowSelection::from_boolean_buffer(mask.clone()))
596 .with_row_selection_policy(RowSelectionPolicy::Auto { threshold: 0 });
597 assert_eq!(
598 disabled.resolve_selection_strategy(),
599 RowSelectionStrategy::Selectors
600 );
601
602 let equal = builder_with_selection(RowSelection::from_boolean_buffer(mask.clone()))
604 .with_row_selection_policy(RowSelectionPolicy::Auto { threshold: 8 });
605 assert_eq!(
606 equal.resolve_selection_strategy(),
607 RowSelectionStrategy::Selectors
608 );
609
610 let above = builder_with_selection(RowSelection::from_boolean_buffer(mask))
611 .with_row_selection_policy(RowSelectionPolicy::Auto { threshold: 9 });
612 assert_eq!(
613 above.resolve_selection_strategy(),
614 RowSelectionStrategy::Mask
615 );
616 }
617
618 #[test]
619 #[cfg_attr(miri, ignore)] fn preferred_selection_strategy_mask_matches_selector_backing() {
621 use rand::{RngExt, rng};
622
623 let mut rand = rng();
624 for _ in 0..200 {
625 let len = rand.random_range(0..256);
626 let bits: Vec<_> = (0..len).map(|_| rand.random_bool(0.5)).collect();
627 let mask_backed = RowSelection::from_boolean_buffer(BooleanBuffer::from(bits.clone()));
628 let selector_backed = RowSelection::from_filters(&[BooleanArray::from(bits)]);
629
630 for threshold in [0, 1, 2, 8, 32, 64, usize::MAX] {
631 assert_eq!(
632 mask_backed.auto_selection_strategy(threshold),
633 selector_backed.auto_selection_strategy(threshold),
634 "strategy differs for len {len} and threshold {threshold}"
635 );
636 }
637 }
638 }
639
640 #[test]
641 fn mask_plan_trims_trailing_skips_before_chunking() {
642 let mut plan = ReadPlanBuilder::new(8)
643 .with_selection(Some(RowSelection::from(vec![
644 RowSelector::select(1),
645 RowSelector::skip(7),
646 ])))
647 .with_row_selection_policy(RowSelectionPolicy::Mask)
648 .build();
649 let RowSelectionCursor::Mask(cursor) = plan.row_selection_cursor_mut() else {
650 panic!("expected a Mask cursor");
651 };
652
653 let chunk = cursor.next_chunk(8).unwrap();
654 assert_eq!(chunk.chunk_rows, 1);
655 assert_eq!(chunk.selected_rows, 1);
656 assert!(cursor.is_empty());
657 }
658
659 #[test]
660 fn selectors_policy_lowers_mask_backed_selection() {
661 let selection = RowSelection::from_boolean_buffer(BooleanBuffer::from(vec![
662 true, false, false, true, true,
663 ]));
664 let mut plan = ReadPlanBuilder::new(4)
665 .with_selection(Some(selection))
666 .with_row_selection_policy(RowSelectionPolicy::Selectors)
667 .build();
668 let RowSelectionCursor::Selectors(cursor) = plan.row_selection_cursor_mut() else {
669 panic!("expected a Selectors cursor");
670 };
671
672 assert_eq!(cursor.next_selector(), RowSelector::select(1));
673 assert_eq!(cursor.next_selector(), RowSelector::skip(2));
674 assert_eq!(cursor.next_selector(), RowSelector::select(2));
675 assert!(cursor.is_empty());
676 }
677
678 #[test]
679 fn mask_backed_plan_respects_loaded_row_ranges() {
680 let mut bits = vec![false; 5];
684 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);
689 let selection = RowSelection::from_boolean_buffer(mask);
690 assert!(selection.as_mask().is_some());
691
692 let loaded = LoadedRowRanges::from_selection(RowSelection::from(vec![
693 RowSelector::select(4),
694 RowSelector::skip(6),
695 RowSelector::select(2),
696 ]));
697
698 let mut plan = ReadPlanBuilder::new(12)
699 .with_selection(Some(selection))
700 .with_row_selection_policy(RowSelectionPolicy::Mask)
701 .with_loaded_row_ranges(Some(loaded))
702 .build();
703 let RowSelectionCursor::Mask(cursor) = plan.row_selection_cursor_mut() else {
704 panic!("expected a Mask cursor");
705 };
706
707 let first = cursor.next_chunk(12).unwrap();
710 assert_eq!(first.initial_skip, 0);
711 assert_eq!(first.chunk_rows, 1);
712 assert_eq!(first.selected_rows, 1);
713
714 let second = cursor.next_chunk(12).unwrap();
716 assert_eq!(second.initial_skip, 10);
717 assert_eq!(second.chunk_rows, 1);
718 assert_eq!(second.selected_rows, 1);
719 assert!(cursor.is_empty());
720 }
721
722 #[test]
723 fn with_predicate_options_capped_auto_preserves_limit_and_padding_boundaries() {
724 let fragmented_early_limit = (0..37)
725 .map(|row| matches!(row, 0 | 3 | 7 | 9 | 12 | 18 | 24 | 36))
726 .collect();
727 assert_limit_case("fragmented early limit", fragmented_early_limit, 16, 3);
728
729 assert_limit_case(
730 "selector-friendly padded tail",
731 vec![true; 4_097],
732 1_024,
733 1_024,
734 );
735 }
736
737 #[test]
738 fn with_predicate_options_limit_pads_tail_when_no_prior_selection() {
739 use crate::arrow::ProjectionMask;
740 use crate::arrow::array_reader::StructArrayReader;
741 use crate::arrow::array_reader::test_util::make_int32_page_reader;
742 use crate::arrow::arrow_reader::ArrowPredicateFn;
743 use arrow_schema::{DataType as ArrowType, Field, Fields};
744
745 const TOTAL_ROWS: usize = 100;
750 const LIMIT: usize = 10;
751
752 let data: Vec<i32> = (0..TOTAL_ROWS as i32).collect();
753 let levels = vec![0; TOTAL_ROWS];
754 let leaf = make_int32_page_reader(&data, &levels, &levels, 0, 0, None);
755 let struct_type = ArrowType::Struct(Fields::from(vec![Field::new(
756 "c0",
757 ArrowType::Int32,
758 false,
759 )]));
760 let struct_reader = StructArrayReader::new(struct_type, vec![leaf], 0, 0, false, None);
761
762 let mut predicate = ArrowPredicateFn::new(ProjectionMask::all(), |batch| {
763 Ok(BooleanArray::from(vec![true; batch.num_rows()]))
764 });
765
766 let builder = ReadPlanBuilder::new(16)
767 .with_predicate_options(
768 PredicateOptions::new(Box::new(struct_reader), &mut predicate)
769 .with_limit(LIMIT, TOTAL_ROWS),
770 )
771 .unwrap();
772
773 let selection = builder
774 .selection()
775 .expect("limit-driven early break must produce a selection");
776
777 assert_eq!(selection.row_count(), LIMIT);
779
780 assert_eq!(
783 selection.total_row_count(),
784 TOTAL_ROWS,
785 "selection must span the full row group, not only the prefix evaluated before the limit"
786 );
787 }
788
789 #[test]
790 fn with_predicate_options_preserves_mask_selection() {
791 use crate::arrow::ProjectionMask;
792 use crate::arrow::array_reader::StructArrayReader;
793 use crate::arrow::array_reader::test_util::make_int32_page_reader;
794 use crate::arrow::arrow_reader::ArrowPredicateFn;
795 use arrow_schema::{DataType as ArrowType, Field, Fields};
796
797 let data: Vec<i32> = (0..6).collect();
798 let levels = vec![0; data.len()];
799 let leaf = make_int32_page_reader(&data, &levels, &levels, 0, 0, None);
800 let struct_type = ArrowType::Struct(Fields::from(vec![Field::new(
801 "c0",
802 ArrowType::Int32,
803 false,
804 )]));
805 let struct_reader = StructArrayReader::new(struct_type, vec![leaf], 0, 0, false, None);
806
807 let prior = RowSelection::from_boolean_buffer(BooleanBuffer::from(vec![
808 true, false, true, true, false, true,
809 ]));
810 let mut filters = vec![BooleanArray::from(vec![true, false, true, false])];
811 let mut predicate = ArrowPredicateFn::new(ProjectionMask::all(), move |batch| {
812 assert_eq!(batch.num_rows(), 4);
813 Ok(filters.remove(0))
814 });
815
816 let builder = ReadPlanBuilder::new(16)
817 .with_selection(Some(prior))
818 .with_predicate_options(PredicateOptions::new(
819 Box::new(struct_reader),
820 &mut predicate,
821 ))
822 .unwrap();
823
824 let selection = builder.selection().unwrap();
825 assert!(selection.as_mask().is_some());
826
827 let expected = RowSelection::from_boolean_buffer(BooleanBuffer::from(vec![
828 true, false, false, true, false, false,
829 ]));
830 assert_eq!(selection, &expected);
831 }
832
833 #[test]
834 fn with_predicate_options_limit_handles_null_filters() {
835 use crate::arrow::ProjectionMask;
836 use crate::arrow::array_reader::StructArrayReader;
837 use crate::arrow::array_reader::test_util::make_int32_page_reader;
838 use crate::arrow::arrow_reader::ArrowPredicateFn;
839 use arrow_schema::{DataType as ArrowType, Field, Fields};
840
841 const TOTAL_ROWS: usize = 100;
842 const LIMIT: usize = 10;
843
844 let data: Vec<i32> = (0..TOTAL_ROWS as i32).collect();
845 let levels = vec![0; TOTAL_ROWS];
846 let leaf = make_int32_page_reader(&data, &levels, &levels, 0, 0, None);
847 let struct_type = ArrowType::Struct(Fields::from(vec![Field::new(
848 "c0",
849 ArrowType::Int32,
850 false,
851 )]));
852 let struct_reader = StructArrayReader::new(struct_type, vec![leaf], 0, 0, false, None);
853
854 let mut predicate = ArrowPredicateFn::new(ProjectionMask::all(), |batch| {
855 Ok((0..batch.num_rows())
856 .map(|i| match i % 4 {
857 0 | 2 => Some(true),
858 1 => None,
859 _ => Some(false),
860 })
861 .collect::<BooleanArray>())
862 });
863
864 let builder = ReadPlanBuilder::new(16)
865 .with_predicate_options(
866 PredicateOptions::new(Box::new(struct_reader), &mut predicate)
867 .with_limit(LIMIT, TOTAL_ROWS),
868 )
869 .unwrap();
870
871 let selection = builder
872 .selection()
873 .expect("limit-driven early break must produce a selection");
874
875 assert_eq!(selection.row_count(), LIMIT);
876
877 assert_eq!(selection.total_row_count(), TOTAL_ROWS);
878 }
879}