Skip to main content

parquet/arrow/push_decoder/reader_builder/
mod.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18mod 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/// The current row group being read, its read plan, and its offset/limit budget.
50#[derive(Debug)]
51struct RowGroupInfo {
52    row_group_idx: usize,
53    row_count: usize,
54    plan_builder: ReadPlanBuilder,
55    budget: RowBudget,
56}
57
58/// This is the inner state machine for reading a single row group.
59#[derive(Debug)]
60enum RowGroupDecoderState {
61    Start {
62        row_group_info: RowGroupInfo,
63    },
64    /// Planning filters, but haven't yet requested data to evaluate them
65    Filters {
66        row_group_info: RowGroupInfo,
67        /// Any previously read column chunk data from prior filters
68        column_chunks: Option<Vec<Option<Arc<ColumnChunkData>>>>,
69        filter_info: FilterInfo,
70    },
71    /// Needs data to evaluate current filter
72    WaitingOnFilterData {
73        row_group_info: RowGroupInfo,
74        filter_info: FilterInfo,
75        data_request: DataRequest,
76    },
77    /// Know what data to actually read, after all predicates
78    StartData {
79        row_group_info: RowGroupInfo,
80        /// Any previously read column chunk data from the filtering phase
81        column_chunks: Option<Vec<Option<Arc<ColumnChunkData>>>>,
82        /// Any cached filter results
83        cache_info: Option<CacheInfo>,
84    },
85    /// Needs data to proceed with reading the output
86    WaitingOnData {
87        row_group_info: RowGroupInfo,
88        data_request: DataRequest,
89        /// Any cached filter results
90        cache_info: Option<CacheInfo>,
91    },
92    /// Finished (or not yet started) reading this group
93    Finished,
94}
95
96impl RowGroupDecoderState {
97    /// The index of the row group, if one is active.
98    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
110/// The byte range of a column chunk in the file.
111pub(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    /// The active row group is complete without producing a reader.
119    Finished {
120        /// Budget remaining after applying this row group's selection.
121        remaining_budget: RowBudget,
122    },
123    /// More bytes are needed before the active row group can make progress.
124    NeedsData(Vec<Range<u64>>),
125    /// The active row group produced a reader.
126    Data {
127        batch_reader: ParquetRecordBatchReader,
128        /// Budget remaining after applying this row group's selection.
129        remaining_budget: RowBudget,
130    },
131}
132
133/// Result of a state transition
134#[derive(Debug)]
135struct NextState {
136    next_state: RowGroupDecoderState,
137    /// result to return, if any
138    ///
139    /// * `Some`: the processing should stop and return the result
140    /// * `None`: processing should continue
141    result: Option<RowGroupBuildResult>,
142}
143
144impl NextState {
145    /// The next state with no result.
146    ///
147    /// This indicates processing should continue
148    fn again(next_state: RowGroupDecoderState) -> Self {
149        Self {
150            next_state,
151            result: None,
152        }
153    }
154
155    /// Create a NextState with a result that should be returned
156    fn result(next_state: RowGroupDecoderState, result: RowGroupBuildResult) -> Self {
157        Self {
158            next_state,
159            result: Some(result),
160        }
161    }
162}
163
164/// Builder for [`ParquetRecordBatchReader`] for a single row group
165///
166/// This struct drives the main state machine for decoding each row group -- it
167/// determines what data is needed, and then assembles the
168/// `ParquetRecordBatchReader` when all data is available.
169#[derive(Debug)]
170pub(crate) struct RowGroupReaderBuilder {
171    /// The output batch size
172    batch_size: usize,
173
174    /// What columns to project (produce in each output batch)
175    projection: ProjectionMask,
176
177    /// The Parquet file metadata
178    metadata: Arc<ParquetMetaData>,
179
180    /// Top level parquet schema and arrow schema mapping
181    fields: Option<Arc<ParquetField>>,
182
183    /// Optional filter
184    filter: Option<RowFilter>,
185
186    /// The size in bytes of the predicate cache to use
187    ///
188    /// See [`RowGroupCache`] for details.
189    max_predicate_cache_size: usize,
190
191    /// The metrics collector
192    metrics: ArrowReaderMetrics,
193
194    /// Strategy for materialising row selections
195    row_selection_policy: RowSelectionPolicy,
196
197    /// Current state of the decoder.
198    ///
199    /// It is taken when processing, and must be put back before returning
200    /// it is a bug error if it is not put back after transitioning states.
201    state: Option<RowGroupDecoderState>,
202
203    /// The underlying data store
204    buffers: PushBuffers,
205
206    /// What each decoding stage fetches. Kept here because the filter is
207    /// moved out of the builder while a row group is decoded.
208    stages: Arc<StageSchedule>,
209}
210
211/// The parts of a [`RowGroupReaderBuilder`] needed to rebuild it, recovered by
212/// [`RowGroupReaderBuilder::into_parts`].
213///
214/// `metadata` is not included: it is a whole-file property carried alongside
215/// `schema` in `RemainingRowGroupsParts`.
216#[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    /// Bytes already pushed into the decoder, carried across a rebuild so they
226    /// are not re-requested.
227    pub buffers: PushBuffers,
228}
229
230impl RowGroupReaderBuilder {
231    /// Create a new RowGroupReaderBuilder
232    #[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    /// Decompose into [`RowGroupReaderBuilderParts`] so the builder can be
278    /// reconstructed. The runtime decode `state` is discarded; `metadata` is
279    /// recovered from the frontier instead (see `RemainingRowGroups::into_parts`).
280    pub(crate) fn into_parts(self) -> RowGroupReaderBuilderParts {
281        // If a new field is added to `RowGroupReaderBuilder`, it must be added here and in `RowGroupReaderBuilderParts`,
282        // or at least evaluate how it should be handled in the decomposition and reconstruction of the builder.
283        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            // Recomputed from `filter` when the builder is rebuilt.
295            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    /// Push new data buffers that can be used to satisfy pending requests
310    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    /// True iff the inner state is `Finished`. This is the only state in
319    /// which it is safe to decompose the builder via [`Self::into_parts`],
320    /// because no `RowGroupInfo`, `FilterInfo`, or in-flight `DataRequest`
321    /// is referencing the row-group-scoped decode state.
322    pub(crate) fn is_finished(&self) -> bool {
323        matches!(self.state, Some(RowGroupDecoderState::Finished))
324    }
325
326    /// Returns the total number of buffered bytes available
327    pub fn buffered_bytes(&self) -> u64 {
328        self.buffers.buffered_bytes()
329    }
330
331    /// Clear any staged ranges currently buffered for future decode work.
332    pub fn clear_all_ranges(&mut self) {
333        self.buffers.clear_all_ranges();
334    }
335
336    /// take the current state, leaving None in its place.
337    ///
338    /// Returns an error if there the state wasn't put back after the previous
339    /// call to [`Self::take_state`].
340    ///
341    /// Any code that calls this method must ensure that the state is put back
342    /// before returning, otherwise the reader will error next time it is called
343    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    /// Returns true if this builder is currently decoding a row group.
352    pub(crate) fn has_active_row_group(&self) -> bool {
353        !matches!(self.state, Some(RowGroupDecoderState::Finished))
354    }
355
356    /// Setup this reader to read the next row group
357    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    /// Try to build the next `ParquetRecordBatchReader` for the active row group.
386    ///
387    /// Returns [`RowGroupBuildResult::NeedsData`] if more data is needed,
388    /// [`RowGroupBuildResult::Data`] if a reader is ready, or
389    /// [`RowGroupBuildResult::Finished`] if the row group completed without
390    /// producing a reader.
391    pub(crate) fn try_build(&mut self) -> Result<RowGroupBuildResult, ParquetError> {
392        loop {
393            let current_state = self.take_state()?;
394            // Try to transition the decoder.
395            match self.try_transition(current_state)? {
396                // Either produced a batch reader, needed input, or finished
397                NextState {
398                    next_state,
399                    result: Some(result),
400                } => {
401                    // put back the next state
402                    self.state = Some(next_state);
403                    return Ok(result);
404                }
405                // completed one internal state, maybe can proceed further
406                NextState {
407                    next_state,
408                    result: None,
409                } => {
410                    // continue processing
411                    self.state = Some(next_state);
412                }
413            }
414        }
415    }
416
417    /// Current state --> next state + optional output
418    ///
419    /// This is the main state transition function for the row group reader
420    /// and encodes the row group decoding state machine.
421    ///
422    /// # Notes
423    ///
424    /// This structure is used to reduce the indentation level of the main loop
425    /// in try_build
426    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; // no prior column chunks
438
439                let Some(filter) = self.filter.take() else {
440                    // no filter, start trying to read data immediately
441                    return Ok(NextState::again(RowGroupDecoderState::StartData {
442                        row_group_info,
443                        column_chunks,
444                        cache_info: None,
445                    }));
446                };
447                // no predicates in filter, so start reading immediately
448                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                // we have predicates to evaluate
457                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            // need to evaluate filters
476            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 nothing is selected, we are done with this row group
489                if !plan_builder.selects_any() {
490                    // ruled out entire row group
491                    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                // Make a request for the data needed to evaluate the current predicate
501                let fetch = self.stages.fetch(Stage::Predicate(filter_info.index()));
502
503                // need to fetch pages the column needs for decoding, figure
504                // that out based on the current selection and projection
505                let data_request = DataRequestBuilder::new(
506                    row_group_idx,
507                    row_count,
508                    self.batch_size,
509                    &self.metadata,
510                    fetch.projection, // use the predicate's projection
511                )
512                .with_selection(plan_builder.selection())
513                // Cached output columns reuse these predicate-stage chunks. Expand their
514                // selection to cache batch boundaries so a cache miss can safely fetch a
515                // complete batch from the retained sparse column data.
516                .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                // figure out what ranges we still need
539                let needed_ranges = data_request.needed_ranges(&self.buffers);
540                if !needed_ranges.is_empty() {
541                    // still need data
542                    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                // otherwise we have all the data we need to evaluate the predicate
553                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                // Auto resolution and loaded ranges are projection-specific, so restore the
579                // configured policy before preparing each predicate.
580                plan_builder = plan_builder.with_row_selection_policy(self.row_selection_policy);
581
582                // Prepare selection execution for pages pruned during fetch.
583                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                // When this is the final predicate in the chain and an output
592                // limit is set, tell the filter evaluation to stop once enough
593                // matching rows have been accumulated.
594                let predicate_limit = filter_info
595                    .is_last()
596                    .then(|| budget.selected_row_limit())
597                    .flatten();
598
599                // Evaluate the filter via `with_predicate_options`, opting into
600                // early termination when this is the final predicate and an
601                // output limit was set.
602                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                // Take back the column chunks that were read
617                let column_chunks = Some(row_group.column_chunks);
618
619                // advance to the next predicate, if any
620                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                    // done with predicates, proceed to reading data
629                    AdvanceResult::Done(filter, cache_info) => {
630                        // remember we need to put back the filter
631                        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                    // ruled out entire row group
662                    return Ok(NextState::result(
663                        RowGroupDecoderState::Finished,
664                        RowGroupBuildResult::Finished { remaining_budget },
665                    ));
666                }
667
668                if rows_after_budget == 0 {
669                    // no rows left after applying limit/offset
670                    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            // Waiting on data to proceed with reading the output
713            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                    // still need data
721                    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                // otherwise we have all the data we need to proceed
732                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                // if we have any cached results, connect them up
750                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    /// The index of the active row group, if any.
782    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    /// Remove the buffered bytes of all column chunks of a row group. This
789    /// includes bytes that the caller pushed but the decoder did not request,
790    /// for example the bytes between the requested ranges of a larger pushed
791    /// buffer.
792    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    /// Remove the buffered bytes outside the read column chunks of
804    /// `row_groups`. A column chunk is read if the output or a predicate
805    /// reads its column. Indexes that are not in the file are ignored.
806    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    /// A [`ScanPlanBuilder`] that plans the same ranges as this builder, for
834    /// the row groups in `frontier`.
835    pub(crate) fn scan_plan_builder(&self, frontier: RowGroupFrontier) -> ScanPlanBuilder {
836        ScanPlanBuilder::new(frontier, self.batch_size, Arc::clone(&self.stages))
837    }
838
839    /// Which columns should be cached?
840    ///
841    /// Returns the columns that are used by the filters *and* then used in the
842    /// final projection, excluding any nested columns.
843    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    /// An associated function, so that [`Self::new`] can call it before the
858    /// builder exists.
859    fn compute_cache_projection_inner(
860        filter: &RowFilter,
861        projection: &ProjectionMask,
862        metadata: &ParquetMetaData,
863        max_predicate_cache_size: usize,
864    ) -> Option<ProjectionMask> {
865        // Do not compute the projection mask if the predicate cache is disabled
866        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        // Exclude leaves belonging to roots that span multiple parquet leaves (i.e. nested columns)
875        cache_projection.without_nested_types(metadata.file_metadata().schema_descr())
876    }
877
878    /// Get the offset index for the specified row group, if any
879    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
892/// Prepare row selection execution when page pruning produced sparse column data.
893///
894/// Some pages can be skipped during row-group construction if they are not read
895/// by the selections. This means that the data pages for those rows are never
896/// loaded and definition/repetition levels are never read. When using
897/// `RowSelections` selection works because `skip_records()` handles this
898/// case and skips the page accordingly.
899///
900/// However, with the current mask design, all values covered by a mask chunk
901/// are decoded before the mask filter is applied. Thus a chunk cannot cross a
902/// page that was skipped during row-group construction.
903///
904/// A simple example:
905/// * the page size is 2, the mask is 100001, row selection should be read(1) skip(4) read(1)
906/// * the `ColumnChunkData` would be page1(10), page2(skipped), page3(01)
907///
908/// Mask execution records the row ranges loaded for every projected column, so
909/// each mask chunk stays within loaded data and `skip_records()` crosses the
910/// gaps. This applies both to an explicit mask policy and to Auto when it
911/// resolves to mask execution.
912fn 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    // With no selection there are no skipped pages and no execution strategy
920    // to prepare. Preserve Auto so a first predicate can choose its backing
921    // while constructing the resulting selection.
922    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
945/// Computes row ranges for which every projected column has page data loaded.
946fn 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    // Verify that the size of RowGroupDecoderState does not grow too large
989    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}