1mod data;
19mod filter;
20mod stages;
21
22use crate::arrow::ProjectionMask;
23use crate::arrow::array_reader::{ArrayReaderBuilder, CacheOptions, RowGroupCache};
24use crate::arrow::arrow_reader::metrics::ArrowReaderMetrics;
25use crate::arrow::arrow_reader::selection::{LoadedRowRanges, RowSelectionStrategy};
26use crate::arrow::arrow_reader::{
27 ParquetRecordBatchReader, PredicateOptions, ReadPlanBuilder, RowFilter, RowSelection,
28 RowSelectionPolicy,
29};
30use crate::arrow::in_memory_row_group::ColumnChunkData;
31use crate::arrow::push_decoder::reader_builder::data::DataRequestBuilder;
32use crate::arrow::push_decoder::reader_builder::filter::CacheInfo;
33use crate::arrow::push_decoder::scan_plan::{
34 BudgetedReadPlan, RowBudget, RowGroupFrontier, ScanPlanBuilder,
35};
36use crate::arrow::schema::ParquetField;
37use crate::errors::ParquetError;
38use crate::file::metadata::page_index::RowGroupPageIndex;
39use crate::file::metadata::{ColumnChunkMetaData, ParquetMetaData};
40use crate::util::push_buffers::PushBuffers;
41use bytes::Bytes;
42use data::DataRequest;
43use filter::AdvanceResult;
44use filter::FilterInfo;
45pub(crate) use stages::{Stage, StageSchedule};
46use std::ops::Range;
47use std::sync::{Arc, RwLock};
48
49#[derive(Debug)]
51struct RowGroupInfo {
52 row_group_idx: usize,
53 row_count: usize,
54 plan_builder: ReadPlanBuilder,
55 budget: RowBudget,
56}
57
58#[derive(Debug)]
60enum RowGroupDecoderState {
61 Start {
62 row_group_info: RowGroupInfo,
63 },
64 Filters {
66 row_group_info: RowGroupInfo,
67 column_chunks: Option<Vec<Option<Arc<ColumnChunkData>>>>,
69 filter_info: FilterInfo,
70 },
71 WaitingOnFilterData {
73 row_group_info: RowGroupInfo,
74 filter_info: FilterInfo,
75 data_request: DataRequest,
76 },
77 StartData {
79 row_group_info: RowGroupInfo,
80 column_chunks: Option<Vec<Option<Arc<ColumnChunkData>>>>,
82 cache_info: Option<CacheInfo>,
84 },
85 WaitingOnData {
87 row_group_info: RowGroupInfo,
88 data_request: DataRequest,
89 cache_info: Option<CacheInfo>,
91 },
92 Finished,
94}
95
96impl RowGroupDecoderState {
97 fn row_group_idx(&self) -> Option<usize> {
99 match self {
100 Self::Start { row_group_info }
101 | Self::Filters { row_group_info, .. }
102 | Self::WaitingOnFilterData { row_group_info, .. }
103 | Self::StartData { row_group_info, .. }
104 | Self::WaitingOnData { row_group_info, .. } => Some(row_group_info.row_group_idx),
105 Self::Finished => None,
106 }
107 }
108}
109
110pub(crate) fn column_chunk_range(column: &ColumnChunkMetaData) -> Range<u64> {
112 let (start, length) = column.byte_range();
113 start..start + length
114}
115
116#[derive(Debug)]
117pub(crate) enum RowGroupBuildResult {
118 Finished {
120 remaining_budget: RowBudget,
122 },
123 NeedsData(Vec<Range<u64>>),
125 Data {
127 batch_reader: ParquetRecordBatchReader,
128 remaining_budget: RowBudget,
130 },
131}
132
133#[derive(Debug)]
135struct NextState {
136 next_state: RowGroupDecoderState,
137 result: Option<RowGroupBuildResult>,
142}
143
144impl NextState {
145 fn again(next_state: RowGroupDecoderState) -> Self {
149 Self {
150 next_state,
151 result: None,
152 }
153 }
154
155 fn result(next_state: RowGroupDecoderState, result: RowGroupBuildResult) -> Self {
157 Self {
158 next_state,
159 result: Some(result),
160 }
161 }
162}
163
164#[derive(Debug)]
170pub(crate) struct RowGroupReaderBuilder {
171 batch_size: usize,
173
174 projection: ProjectionMask,
176
177 metadata: Arc<ParquetMetaData>,
179
180 fields: Option<Arc<ParquetField>>,
182
183 filter: Option<RowFilter>,
185
186 max_predicate_cache_size: usize,
190
191 metrics: ArrowReaderMetrics,
193
194 row_selection_policy: RowSelectionPolicy,
196
197 state: Option<RowGroupDecoderState>,
202
203 buffers: PushBuffers,
205
206 stages: Arc<StageSchedule>,
209}
210
211#[derive(Debug)]
217pub(crate) struct RowGroupReaderBuilderParts {
218 pub batch_size: usize,
219 pub projection: ProjectionMask,
220 pub fields: Option<Arc<ParquetField>>,
221 pub filter: Option<RowFilter>,
222 pub max_predicate_cache_size: usize,
223 pub metrics: ArrowReaderMetrics,
224 pub row_selection_policy: RowSelectionPolicy,
225 pub buffers: PushBuffers,
228}
229
230impl RowGroupReaderBuilder {
231 #[expect(clippy::too_many_arguments)]
233 pub(crate) fn new(
234 batch_size: usize,
235 projection: ProjectionMask,
236 metadata: Arc<ParquetMetaData>,
237 fields: Option<Arc<ParquetField>>,
238 filter: Option<RowFilter>,
239 metrics: ArrowReaderMetrics,
240 max_predicate_cache_size: usize,
241 buffers: PushBuffers,
242 row_selection_policy: RowSelectionPolicy,
243 ) -> Self {
244 let (predicate_projections, cache_projection) = match &filter {
245 Some(filter) => (
246 filter
247 .predicates
248 .iter()
249 .map(|predicate| predicate.projection().clone())
250 .collect(),
251 Self::compute_cache_projection_inner(
252 filter,
253 &projection,
254 &metadata,
255 max_predicate_cache_size,
256 ),
257 ),
258 None => (vec![], None),
259 };
260 let stages =
261 StageSchedule::new(projection.clone(), predicate_projections, cache_projection);
262 Self {
263 batch_size,
264 projection,
265 metadata,
266 fields,
267 filter,
268 metrics,
269 max_predicate_cache_size,
270 row_selection_policy,
271 state: Some(RowGroupDecoderState::Finished),
272 buffers,
273 stages: Arc::new(stages),
274 }
275 }
276
277 pub(crate) fn into_parts(self) -> RowGroupReaderBuilderParts {
281 let Self {
284 batch_size,
285 projection,
286 metadata: _,
287 fields,
288 filter,
289 max_predicate_cache_size,
290 metrics,
291 row_selection_policy,
292 state: _,
293 buffers,
294 stages: _,
296 } = self;
297 RowGroupReaderBuilderParts {
298 batch_size,
299 projection,
300 fields,
301 filter,
302 max_predicate_cache_size,
303 metrics,
304 row_selection_policy,
305 buffers,
306 }
307 }
308
309 pub fn push_data(
311 &mut self,
312 ranges: Vec<Range<u64>>,
313 buffers: Vec<Bytes>,
314 ) -> Result<(), ParquetError> {
315 self.buffers.push_ranges(ranges, buffers)
316 }
317
318 pub(crate) fn is_finished(&self) -> bool {
323 matches!(self.state, Some(RowGroupDecoderState::Finished))
324 }
325
326 pub fn buffered_bytes(&self) -> u64 {
328 self.buffers.buffered_bytes()
329 }
330
331 pub fn clear_all_ranges(&mut self) {
333 self.buffers.clear_all_ranges();
334 }
335
336 fn take_state(&mut self) -> Result<RowGroupDecoderState, ParquetError> {
344 self.state.take().ok_or_else(|| {
345 ParquetError::General(String::from(
346 "Internal Error: RowGroupReader in invalid state",
347 ))
348 })
349 }
350
351 pub(crate) fn has_active_row_group(&self) -> bool {
353 !matches!(self.state, Some(RowGroupDecoderState::Finished))
354 }
355
356 pub(crate) fn next_row_group(
358 &mut self,
359 row_group_idx: usize,
360 row_count: usize,
361 selection: Option<RowSelection>,
362 budget: RowBudget,
363 ) -> Result<(), ParquetError> {
364 let state = self.take_state()?;
365 if !matches!(state, RowGroupDecoderState::Finished) {
366 return Err(ParquetError::General(format!(
367 "Internal Error: next_row_group called while still reading a row group. Expected Finished state, got {state:?}"
368 )));
369 }
370 let plan_builder = ReadPlanBuilder::new(self.batch_size)
371 .with_selection(selection)
372 .with_row_selection_policy(self.row_selection_policy);
373
374 let row_group_info = RowGroupInfo {
375 row_group_idx,
376 row_count,
377 plan_builder,
378 budget,
379 };
380
381 self.state = Some(RowGroupDecoderState::Start { row_group_info });
382 Ok(())
383 }
384
385 pub(crate) fn try_build(&mut self) -> Result<RowGroupBuildResult, ParquetError> {
392 loop {
393 let current_state = self.take_state()?;
394 match self.try_transition(current_state)? {
396 NextState {
398 next_state,
399 result: Some(result),
400 } => {
401 self.state = Some(next_state);
403 return Ok(result);
404 }
405 NextState {
407 next_state,
408 result: None,
409 } => {
410 self.state = Some(next_state);
412 }
413 }
414 }
415 }
416
417 fn try_transition(
427 &mut self,
428 current_state: RowGroupDecoderState,
429 ) -> Result<NextState, ParquetError> {
430 let result = match current_state {
431 RowGroupDecoderState::Start { row_group_info } => {
432 debug_assert!(
433 !row_group_info.budget.is_exhausted(),
434 "RowGroupFrontier should not hand off row groups after the output limit is exhausted"
435 );
436
437 let column_chunks = None; let Some(filter) = self.filter.take() else {
440 return Ok(NextState::again(RowGroupDecoderState::StartData {
442 row_group_info,
443 column_chunks,
444 cache_info: None,
445 }));
446 };
447 if filter.predicates.is_empty() {
449 return Ok(NextState::again(RowGroupDecoderState::StartData {
450 row_group_info,
451 column_chunks,
452 cache_info: None,
453 }));
454 }
455
456 let cache_projection =
458 self.compute_cache_projection(row_group_info.row_group_idx, &filter);
459
460 let cache_info = CacheInfo::new(
461 cache_projection,
462 Arc::new(RwLock::new(RowGroupCache::new(
463 self.batch_size,
464 self.max_predicate_cache_size,
465 ))),
466 );
467
468 let filter_info = FilterInfo::new(filter, cache_info);
469 NextState::again(RowGroupDecoderState::Filters {
470 row_group_info,
471 filter_info,
472 column_chunks,
473 })
474 }
475 RowGroupDecoderState::Filters {
477 row_group_info,
478 column_chunks,
479 filter_info,
480 } => {
481 let RowGroupInfo {
482 row_group_idx,
483 row_count,
484 plan_builder,
485 budget,
486 } = row_group_info;
487
488 if !plan_builder.selects_any() {
490 self.filter = Some(filter_info.into_filter());
492 return Ok(NextState::result(
493 RowGroupDecoderState::Finished,
494 RowGroupBuildResult::Finished {
495 remaining_budget: budget,
496 },
497 ));
498 }
499
500 let fetch = self.stages.fetch(Stage::Predicate(filter_info.index()));
502
503 let data_request = DataRequestBuilder::new(
506 row_group_idx,
507 row_count,
508 self.batch_size,
509 &self.metadata,
510 fetch.projection, )
512 .with_selection(plan_builder.selection())
513 .with_cache_projection(fetch.cache_projection)
517 .with_column_chunks(column_chunks)
518 .build();
519
520 let row_group_info = RowGroupInfo {
521 row_group_idx,
522 row_count,
523 plan_builder,
524 budget,
525 };
526
527 NextState::again(RowGroupDecoderState::WaitingOnFilterData {
528 row_group_info,
529 filter_info,
530 data_request,
531 })
532 }
533 RowGroupDecoderState::WaitingOnFilterData {
534 row_group_info,
535 data_request,
536 mut filter_info,
537 } => {
538 let needed_ranges = data_request.needed_ranges(&self.buffers);
540 if !needed_ranges.is_empty() {
541 return Ok(NextState::result(
543 RowGroupDecoderState::WaitingOnFilterData {
544 row_group_info,
545 filter_info,
546 data_request,
547 },
548 RowGroupBuildResult::NeedsData(needed_ranges),
549 ));
550 }
551
552 let RowGroupInfo {
554 row_group_idx,
555 row_count,
556 mut plan_builder,
557 budget,
558 } = row_group_info;
559
560 let predicate = filter_info.current();
561
562 let row_group = data_request.try_into_in_memory_row_group(
563 row_group_idx,
564 row_count,
565 &self.metadata,
566 predicate.projection(),
567 &mut self.buffers,
568 )?;
569
570 let cache_options = filter_info.cache_builder().producer();
571
572 let array_reader = ArrayReaderBuilder::new(&row_group, &self.metrics)
573 .with_batch_size(self.batch_size)
574 .with_cache_options(Some(&cache_options))
575 .with_parquet_metadata(&self.metadata)
576 .build_array_reader(self.fields.as_deref(), predicate.projection())?;
577
578 plan_builder = plan_builder.with_row_selection_policy(self.row_selection_policy);
581
582 plan_builder = prepare_selection_for_page_skipping(
584 plan_builder,
585 predicate.projection(),
586 self.row_group_offset_index(row_group_idx),
587 self.metadata.file_metadata().schema_descr().num_columns(),
588 row_count,
589 );
590
591 let predicate_limit = filter_info
595 .is_last()
596 .then(|| budget.selected_row_limit())
597 .flatten();
598
599 let mut predicate_options =
603 PredicateOptions::new(array_reader, filter_info.current_mut());
604 if let Some(limit) = predicate_limit {
605 predicate_options = predicate_options.with_limit(limit, row_count);
606 }
607 plan_builder = plan_builder.with_predicate_options(predicate_options)?;
608
609 let row_group_info = RowGroupInfo {
610 row_group_idx,
611 row_count,
612 plan_builder,
613 budget,
614 };
615
616 let column_chunks = Some(row_group.column_chunks);
618
619 match filter_info.advance() {
621 AdvanceResult::Continue(filter_info) => {
622 NextState::again(RowGroupDecoderState::Filters {
623 row_group_info,
624 column_chunks,
625 filter_info,
626 })
627 }
628 AdvanceResult::Done(filter, cache_info) => {
630 assert!(self.filter.is_none());
632 self.filter = Some(filter);
633 NextState::again(RowGroupDecoderState::StartData {
634 row_group_info,
635 column_chunks,
636 cache_info: Some(cache_info),
637 })
638 }
639 }
640 }
641 RowGroupDecoderState::StartData {
642 row_group_info,
643 column_chunks,
644 cache_info,
645 } => {
646 let RowGroupInfo {
647 row_group_idx,
648 row_count,
649 plan_builder,
650 budget,
651 } = row_group_info;
652
653 let BudgetedReadPlan {
654 mut plan_builder,
655 rows_before_budget,
656 rows_after_budget,
657 remaining_budget,
658 } = budget.apply_to_plan(plan_builder, row_count);
659
660 if rows_before_budget == 0 {
661 return Ok(NextState::result(
663 RowGroupDecoderState::Finished,
664 RowGroupBuildResult::Finished { remaining_budget },
665 ));
666 }
667
668 if rows_after_budget == 0 {
669 return Ok(NextState::result(
671 RowGroupDecoderState::Finished,
672 RowGroupBuildResult::Finished { remaining_budget },
673 ));
674 }
675
676 let fetch = self.stages.fetch(Stage::Projection);
677 let data_request = DataRequestBuilder::new(
678 row_group_idx,
679 row_count,
680 self.batch_size,
681 &self.metadata,
682 fetch.projection,
683 )
684 .with_selection(plan_builder.selection())
685 .with_column_chunks(column_chunks)
686 .with_cache_projection(fetch.cache_projection)
687 .build();
688
689 plan_builder = plan_builder.with_row_selection_policy(self.row_selection_policy);
690
691 plan_builder = prepare_selection_for_page_skipping(
692 plan_builder,
693 &self.projection,
694 self.row_group_offset_index(row_group_idx),
695 self.metadata.file_metadata().schema_descr().num_columns(),
696 row_count,
697 );
698
699 let row_group_info = RowGroupInfo {
700 row_group_idx,
701 row_count,
702 plan_builder,
703 budget: remaining_budget,
704 };
705
706 NextState::again(RowGroupDecoderState::WaitingOnData {
707 row_group_info,
708 data_request,
709 cache_info,
710 })
711 }
712 RowGroupDecoderState::WaitingOnData {
714 row_group_info,
715 data_request,
716 cache_info,
717 } => {
718 let needed_ranges = data_request.needed_ranges(&self.buffers);
719 if !needed_ranges.is_empty() {
720 return Ok(NextState::result(
722 RowGroupDecoderState::WaitingOnData {
723 row_group_info,
724 data_request,
725 cache_info,
726 },
727 RowGroupBuildResult::NeedsData(needed_ranges),
728 ));
729 }
730
731 let RowGroupInfo {
733 row_group_idx,
734 row_count,
735 plan_builder,
736 budget,
737 } = row_group_info;
738
739 let row_group = data_request.try_into_in_memory_row_group(
740 row_group_idx,
741 row_count,
742 &self.metadata,
743 &self.projection,
744 &mut self.buffers,
745 )?;
746
747 let plan = plan_builder.build();
748
749 let array_reader_builder = ArrayReaderBuilder::new(&row_group, &self.metrics)
751 .with_batch_size(self.batch_size)
752 .with_parquet_metadata(&self.metadata);
753 let array_reader = if let Some(cache_info) = cache_info.as_ref() {
754 let cache_options: CacheOptions = cache_info.builder().consumer();
755 array_reader_builder
756 .with_cache_options(Some(&cache_options))
757 .build_array_reader(self.fields.as_deref(), &self.projection)
758 } else {
759 array_reader_builder
760 .build_array_reader(self.fields.as_deref(), &self.projection)
761 }?;
762
763 let reader = ParquetRecordBatchReader::new(array_reader, plan);
764 NextState::result(
765 RowGroupDecoderState::Finished,
766 RowGroupBuildResult::Data {
767 batch_reader: reader,
768 remaining_budget: budget,
769 },
770 )
771 }
772 RowGroupDecoderState::Finished => {
773 return Err(ParquetError::General(String::from(
774 "Internal Error: try_build called without an active row group",
775 )));
776 }
777 };
778 Ok(result)
779 }
780
781 pub(crate) fn active_row_group_idx(&self) -> Option<usize> {
783 self.state
784 .as_ref()
785 .and_then(RowGroupDecoderState::row_group_idx)
786 }
787
788 pub(crate) fn release_row_group(&mut self, row_group_idx: usize) {
793 let ranges: Vec<Range<u64>> = self
794 .metadata
795 .row_group(row_group_idx)
796 .columns()
797 .iter()
798 .map(column_chunk_range)
799 .collect();
800 self.buffers.release_ranges(&ranges);
801 }
802
803 pub(crate) fn release_unread_bytes(&mut self, row_groups: impl IntoIterator<Item = usize>) {
807 if self.buffers.buffered_bytes() == 0 {
808 return;
809 }
810 let mut read_columns = self.projection.clone();
811 if let Some(filter) = &self.filter {
812 for predicate in &filter.predicates {
813 read_columns.union(predicate.projection());
814 }
815 }
816 let mut keep = vec![];
817 for row_group_idx in row_groups {
818 let Some(row_group) = self.metadata.row_groups().get(row_group_idx) else {
819 continue;
820 };
821 keep.extend(
822 row_group
823 .columns()
824 .iter()
825 .enumerate()
826 .filter(|(column_idx, _)| read_columns.leaf_included(*column_idx))
827 .map(|(_, column)| column_chunk_range(column)),
828 );
829 }
830 self.buffers.retain_ranges(&keep);
831 }
832
833 pub(crate) fn scan_plan_builder(&self, frontier: RowGroupFrontier) -> ScanPlanBuilder {
836 ScanPlanBuilder::new(frontier, self.batch_size, Arc::clone(&self.stages))
837 }
838
839 fn compute_cache_projection(&self, row_group_idx: usize, filter: &RowFilter) -> ProjectionMask {
844 let meta = self.metadata.row_group(row_group_idx);
845 let cache_projection = Self::compute_cache_projection_inner(
846 filter,
847 &self.projection,
848 &self.metadata,
849 self.max_predicate_cache_size,
850 );
851 match cache_projection {
852 Some(projection) => projection,
853 None => ProjectionMask::none(meta.columns().len()),
854 }
855 }
856
857 fn compute_cache_projection_inner(
860 filter: &RowFilter,
861 projection: &ProjectionMask,
862 metadata: &ParquetMetaData,
863 max_predicate_cache_size: usize,
864 ) -> Option<ProjectionMask> {
865 if max_predicate_cache_size == 0 {
867 return None;
868 }
869 let mut cache_projection = filter.predicates.first()?.projection().clone();
870 for predicate in &filter.predicates {
871 cache_projection.union(predicate.projection());
872 }
873 cache_projection.intersect(projection);
874 cache_projection.without_nested_types(metadata.file_metadata().schema_descr())
876 }
877
878 fn row_group_offset_index(&self, row_group_idx: usize) -> Option<RowGroupPageIndex> {
880 if self
881 .metadata
882 .page_index()
883 .is_some_and(|pi| pi.has_offset_indexes())
884 {
885 Some(self.metadata.page_index_for_row_group(row_group_idx))
886 } else {
887 None
888 }
889 }
890}
891
892fn prepare_selection_for_page_skipping(
913 plan_builder: ReadPlanBuilder,
914 projection_mask: &ProjectionMask,
915 page_index: Option<RowGroupPageIndex>,
916 num_columns: usize,
917 total_rows: usize,
918) -> ReadPlanBuilder {
919 if plan_builder.selection().is_none() {
923 return plan_builder;
924 }
925
926 match plan_builder.resolve_selection_strategy() {
927 RowSelectionStrategy::Mask => {
928 let loaded = loaded_row_ranges_for_projection(
929 plan_builder.selection(),
930 projection_mask,
931 page_index,
932 num_columns,
933 total_rows,
934 );
935 plan_builder
936 .with_row_selection_policy(RowSelectionPolicy::Mask)
937 .with_loaded_row_ranges(loaded)
938 }
939 RowSelectionStrategy::Selectors => {
940 plan_builder.with_row_selection_policy(RowSelectionPolicy::Selectors)
941 }
942 }
943}
944
945fn loaded_row_ranges_for_projection(
947 selection: Option<&RowSelection>,
948 projection_mask: &ProjectionMask,
949 page_index: Option<RowGroupPageIndex>,
950 num_columns: usize,
951 total_rows: usize,
952) -> Option<LoadedRowRanges> {
953 let selection = selection?;
954 let page_index = page_index?;
955
956 (0..num_columns)
957 .into_iter()
958 .filter_map(|leaf_idx| {
959 let column_metadata = page_index.offset_index(leaf_idx)?;
960 let pages = column_metadata.page_locations();
961 (projection_mask.leaf_included(leaf_idx) && !pages.is_empty()).then(|| {
962 RowSelection::from_consecutive_ranges(
963 selection
964 .row_ranges_for_selected_pages(pages, total_rows)
965 .into_iter(),
966 total_rows,
967 )
968 })
969 })
970 .reduce(|loaded, column| loaded.intersection(&column))
971 .filter(|loaded| loaded.skipped_row_count() != 0)
972 .map(LoadedRowRanges::from_selection)
973}
974
975#[cfg(test)]
976mod tests {
977 use super::*;
978 use crate::arrow::array_reader::StructArrayReader;
979 use crate::arrow::array_reader::test_util::make_int32_page_reader;
980 use crate::arrow::arrow_reader::ArrowPredicateFn;
981 use crate::arrow::arrow_reader::{RowSelection, RowSelector};
982 use crate::file::metadata::page_index::{PageIndexBuilder, PageIndexProvider};
983 use crate::file::page_index::offset_index::{OffsetIndexMetaData, PageLocation};
984 use arrow_array::BooleanArray;
985 use arrow_schema::{DataType as ArrowType, Field, Fields};
986
987 #[test]
988 fn test_structure_size() {
990 assert_eq!(std::mem::size_of::<RowGroupDecoderState>(), 240);
991 }
992
993 #[test]
994 fn test_loaded_row_ranges_intersect_column_page_boundaries() {
995 let mut page_index = PageIndexBuilder::new(1, 2);
996 let column = |first_rows: &[i64]| OffsetIndexMetaData {
997 page_locations: first_rows
998 .iter()
999 .enumerate()
1000 .map(|(idx, first_row_index)| PageLocation {
1001 offset: (idx * 10) as i64,
1002 compressed_page_size: 10,
1003 first_row_index: *first_row_index,
1004 })
1005 .collect(),
1006 unencoded_byte_array_data_bytes: None,
1007 };
1008 page_index.put_offset_index(column(&[0, 4, 8]), 0, 0);
1009 page_index.put_offset_index(column(&[0, 6, 10]), 0, 1);
1010 let page_index: Option<Arc<dyn PageIndexProvider>> = Some(Arc::new(page_index.build()));
1011 let page_index = RowGroupPageIndex::new(0, page_index);
1012 let selection = RowSelection::from(vec![
1013 RowSelector::skip(1),
1014 RowSelector::select(1),
1015 RowSelector::skip(9),
1016 RowSelector::select(1),
1017 ]);
1018
1019 let loaded = loaded_row_ranges_for_projection(
1020 Some(&selection),
1021 &ProjectionMask::all(),
1022 Some(page_index),
1023 2,
1024 12,
1025 )
1026 .unwrap();
1027
1028 assert_eq!(loaded.ranges(), &[0..4, 10..12]);
1029 }
1030
1031 #[test]
1032 fn test_page_skipping_preparation_preserves_first_predicate_auto_mask() {
1033 let policy = RowSelectionPolicy::Auto { threshold: 4 };
1034 let plan_builder = ReadPlanBuilder::new(4).with_row_selection_policy(policy);
1035
1036 let prepared =
1037 prepare_selection_for_page_skipping(plan_builder, &ProjectionMask::all(), None, 1, 12);
1038 assert_eq!(prepared.row_selection_policy(), &policy);
1039 assert!(prepared.selection().is_none());
1040
1041 let data: Vec<i32> = (0..12).collect();
1042 let levels = vec![0; data.len()];
1043 let leaf = make_int32_page_reader(&data, &levels, &levels, 0, 0, None);
1044 let struct_type = ArrowType::Struct(Fields::from(vec![Field::new(
1045 "c0",
1046 ArrowType::Int32,
1047 false,
1048 )]));
1049 let struct_reader = StructArrayReader::new(struct_type, vec![leaf], 0, 0, false, None);
1050 let mut offset = 0usize;
1051 let mut predicate = ArrowPredicateFn::new(ProjectionMask::all(), move |batch| {
1052 let end = offset + batch.num_rows();
1053 let filter =
1054 BooleanArray::from((offset..end).map(|row| row % 2 == 0).collect::<Vec<_>>());
1055 offset = end;
1056 Ok(filter)
1057 });
1058
1059 let prepared = prepared
1060 .with_predicate_options(PredicateOptions::new(
1061 Box::new(struct_reader),
1062 &mut predicate,
1063 ))
1064 .unwrap();
1065 let selection = prepared.selection().expect("first predicate selection");
1066 let reference = RowSelection::from_filters(&[BooleanArray::from(
1067 (0..12).map(|row| row % 2 == 0).collect::<Vec<_>>(),
1068 )]);
1069
1070 assert_eq!(selection, &reference);
1071 assert!(selection.as_mask().is_some());
1072 }
1073
1074 #[test]
1075 fn test_auto_keeps_mask_when_page_pruning_skips_pages() {
1076 let mut page_index = PageIndexBuilder::new(1, 1);
1077 page_index.put_offset_index(
1078 OffsetIndexMetaData {
1079 page_locations: [0, 2, 4, 6, 8, 10]
1080 .into_iter()
1081 .enumerate()
1082 .map(|(idx, first_row_index)| PageLocation {
1083 offset: (idx * 10) as i64,
1084 compressed_page_size: 10,
1085 first_row_index,
1086 })
1087 .collect(),
1088 unencoded_byte_array_data_bytes: None,
1089 },
1090 0,
1091 0,
1092 );
1093 let page_index: Option<Arc<dyn PageIndexProvider>> = Some(Arc::new(page_index.build()));
1094 let page_index = RowGroupPageIndex::new(0, page_index);
1095 let selection = RowSelection::from(vec![
1096 RowSelector::select(1),
1097 RowSelector::skip(10),
1098 RowSelector::select(1),
1099 ]);
1100 let plan_builder = ReadPlanBuilder::new(12)
1101 .with_selection(Some(selection))
1102 .with_row_selection_policy(RowSelectionPolicy::Auto { threshold: 32 });
1103
1104 let prepared = prepare_selection_for_page_skipping(
1105 plan_builder,
1106 &ProjectionMask::all(),
1107 Some(page_index),
1108 1,
1109 12,
1110 );
1111
1112 assert_eq!(prepared.row_selection_policy(), &RowSelectionPolicy::Mask);
1113 }
1114}