Skip to main content

parquet/arrow/push_decoder/scan_plan/
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
18//! [`ScanPlan`]: the byte ranges that a push decoder may still read.
19
20use std::cmp::Reverse;
21use std::collections::BinaryHeap;
22use std::iter::FusedIterator;
23use std::ops::Range;
24use std::sync::Arc;
25
26use super::reader_builder::StageSchedule;
27use crate::arrow::arrow_reader::{ReadPlanBuilder, RowSelection};
28use crate::arrow::in_memory_row_group::{
29    ColumnFetch, column_selection, columns_to_fetch, dictionary_range, page_range,
30};
31use crate::errors::ParquetError;
32use crate::file::metadata::ParquetMetaData;
33use crate::file::metadata::page_index::PageIndexProvider;
34use crate::file::page_index::offset_index::PageLocation;
35
36mod budget;
37mod frontier;
38
39pub(crate) use budget::{BudgetedReadPlan, RowBudget};
40pub(crate) use frontier::{NextRowGroup, RowGroupFrontier};
41
42/// One item of a [`ScanPlan`].
43///
44/// Two items are equal if their public fields are equal.
45#[derive(Debug, Clone)]
46#[non_exhaustive]
47pub struct PlannedRange {
48    /// Byte range in the file.
49    pub range: Range<u64>,
50    /// Row group index in the file.
51    pub row_group: usize,
52    /// Leaf column index in the file.
53    pub column: usize,
54    /// What the range contains.
55    pub kind: PageKind,
56    /// First planned row that this range serves, counted from the start of
57    /// the plan. The tests use it to check the order of the plan.
58    #[cfg_attr(not(test), expect(dead_code))]
59    pub(crate) first_row: u64,
60    /// One past the last planned row that this range serves.
61    #[cfg_attr(not(test), expect(dead_code))]
62    pub(crate) last_row: u64,
63}
64
65/// Compares the public fields only. `first_row` and `last_row` count from
66/// the start of a plan, so they differ between two plans of the same decoder.
67impl PartialEq for PlannedRange {
68    fn eq(&self, other: &Self) -> bool {
69        let Self {
70            range,
71            row_group,
72            column,
73            kind,
74            first_row: _,
75            last_row: _,
76        } = self;
77        *range == other.range
78            && *row_group == other.row_group
79            && *column == other.column
80            && *kind == other.kind
81    }
82}
83
84impl Eq for PlannedRange {}
85
86impl PlannedRange {
87    /// Number of bytes in this range.
88    pub fn len(&self) -> u64 {
89        self.range.end - self.range.start
90    }
91
92    /// Returns `true` if this range contains no bytes.
93    pub fn is_empty(&self) -> bool {
94        self.range.is_empty()
95    }
96}
97
98/// What a [`PlannedRange`] contains.
99#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
100#[non_exhaustive]
101pub enum PageKind {
102    /// The dictionary page of a column chunk.
103    Dictionary,
104    /// One data page.
105    Data,
106    /// A complete column chunk. The plan uses this when the column has no
107    /// offset index, so page locations are unknown.
108    ColumnChunk,
109}
110
111/// A decoding stage of a row group.
112///
113/// The decoder reads a column once per row group. A column that a predicate
114/// reads is not read again for the output.
115pub(crate) use super::reader_builder::Stage as ScanStage;
116
117/// The byte ranges that a push decoder may still read, in the order that
118/// decoding needs them.
119///
120/// Created by [`ParquetPushDecoder::scan_plan`]. Use it to fetch data before
121/// the decoder asks for it.
122///
123/// A `ScanPlan` is an estimate of the ranges that the decoder may need. The
124/// ranges in [`DecodeResult::NeedsData`] from
125/// [`ParquetPushDecoder::try_decode`] are the ranges that it actually needs.
126///
127/// * The plan covers every byte that the decoder can request after the
128///   plan is made. It can cover more, for example ranges that a
129///   [`RowFilter`] makes unnecessary. One request can span more than one
130///   planned range: without a row selection, the decoder requests a complete
131///   column chunk, and the plan lists the pages of the column chunk.
132/// * The ranges are generated on demand, as you iterate. The plan does not
133///   calculate a range until you ask for it.
134/// * The plan comes from the current state of the decoder. To plan the
135///   whole scan, call [`ParquetPushDecoder::scan_plan`] once after
136///   [`ParquetPushDecoderBuilder::build`] and keep the iterator. If you
137///   rebuild the decoder with [`ParquetPushDecoder::into_builder`], the old
138///   plan is not valid for the new decoder. Call `scan_plan` again on the new
139///   decoder.
140/// * Row groups are in read order. In a row group, the columns of the
141///   [`RowFilter`] predicates come first, then the other output columns. The
142///   ranges are ordered by the first row that they serve, and the ranges of
143///   one column chunk stay in file order.
144/// * There is one range for each page if the column has an offset index,
145///   and one range for the entire column chunk if not.
146/// * The plan does not change decoding, does no I/O and does not depend
147///   on pushed data.
148/// * Without a row selection, the state of the plan does not grow with the
149///   number of pages. With a row selection, the plan keeps the indexes of
150///   the selected pages of each column chunk in the current decoding stage
151///   of the current row group.
152/// * If the decoder will return an error for a row group, the plan ends
153///   before that row group. An example is a row selection with more rows
154///   than the row group.
155///
156/// Keep the fetched ranges in your own cache, and answer each `NeedsData`
157/// with exactly the requested ranges. See [`ParquetPushDecoder::push_ranges`].
158///
159/// # Example
160///
161/// ```
162/// # use std::collections::BTreeMap;
163/// # use std::ops::Range;
164/// # use bytes::Bytes;
165/// # use arrow_array::record_batch;
166/// # use parquet::DecodeResult;
167/// # use parquet::arrow::ArrowWriter;
168/// # use parquet::arrow::arrow_reader::{ArrowReaderMetadata, ArrowReaderOptions};
169/// # use parquet::arrow::push_decoder::ParquetPushDecoderBuilder;
170/// # use parquet::file::metadata::PageIndexPolicy;
171/// # use parquet::file::properties::WriterProperties;
172/// # let file = {
173/// #   let mut buffer = vec![];
174/// #   let batch = record_batch!(("a", Int32, [1, 2, 3, 4])).unwrap();
175/// #   let props = WriterProperties::builder().set_max_row_group_row_count(Some(2)).build();
176/// #   let mut writer = ArrowWriter::try_new(&mut buffer, batch.schema(), Some(props)).unwrap();
177/// #   writer.write(&batch).unwrap();
178/// #   writer.close().unwrap();
179/// #   Bytes::from(buffer)
180/// # };
181/// # let fetch = |range: &Range<u64>| file.slice(range.start as usize..range.end as usize);
182/// # // Join cached ranges that cover `range`, or fetch it.
183/// # let read = |cache: &BTreeMap<u64, Bytes>, range: &Range<u64>| {
184/// #     let mut data = Vec::new();
185/// #     let mut position = range.start;
186/// #     while position < range.end {
187/// #         let Some((start, bytes)) = cache.range(..=position).next_back() else {
188/// #             return fetch(range);
189/// #         };
190/// #         let end = (start + bytes.len() as u64).min(range.end);
191/// #         if end <= position {
192/// #             return fetch(range);
193/// #         }
194/// #         data.extend_from_slice(&bytes[(position - start) as usize..(end - start) as usize]);
195/// #         position = end;
196/// #     }
197/// #     Bytes::from(data)
198/// # };
199/// # let options = ArrowReaderOptions::new().with_page_index_policy(PageIndexPolicy::Optional);
200/// # let metadata = ArrowReaderMetadata::load(&file, options).unwrap();
201/// let mut decoder = ParquetPushDecoderBuilder::new_with_metadata(metadata)
202///     .build()
203///     .unwrap();
204///
205/// // Read ahead up to 1 MB into a cache
206/// let mut cache = BTreeMap::new();
207/// let mut cached = 0;
208/// for planned in decoder.scan_plan() {
209///     if cached + planned.len() > 1024 * 1024 {
210///         break;
211///     }
212///     cached += planned.len();
213///     cache.insert(planned.range.start, fetch(&planned.range));
214/// }
215///
216/// // Answer each request from the cache
217/// loop {
218///     match decoder.try_decode().unwrap() {
219///         DecodeResult::NeedsData(ranges) => {
220///             // `read` takes the data from the cache. If a range is not in
221///             // the cache, it fetches it here.
222///             let data = ranges.iter().map(|range| read(&cache, range)).collect();
223///             decoder.push_ranges(ranges, data).unwrap();
224///         }
225///         DecodeResult::Data(batch) => println!("{} rows", batch.num_rows()),
226///         DecodeResult::Finished => break,
227///     }
228/// }
229/// ```
230///
231/// [`RowFilter`]: crate::arrow::arrow_reader::RowFilter
232/// [`DecodeResult::NeedsData`]: crate::DecodeResult::NeedsData
233/// [`ParquetPushDecoder::scan_plan`]: super::ParquetPushDecoder::scan_plan
234/// [`ParquetPushDecoder::into_builder`]: super::ParquetPushDecoder::into_builder
235/// [`ParquetPushDecoder::try_decode`]: super::ParquetPushDecoder::try_decode
236/// [`ParquetPushDecoder::push_ranges`]: super::ParquetPushDecoder::push_ranges
237/// [`ParquetPushDecoderBuilder::build`]: super::ParquetPushDecoderBuilder::build
238#[derive(Debug, Clone)]
239pub struct ScanPlan {
240    /// `None` for an empty plan, for example of a finished decoder.
241    planner: Option<Box<Planner>>,
242}
243
244impl ScanPlan {
245    /// A plan with no ranges.
246    pub(crate) fn empty() -> Self {
247        Self { planner: None }
248    }
249}
250
251/// The state of a [`ScanPlan`] that is not empty.
252#[derive(Debug, Clone)]
253struct Planner {
254    /// The decoder's row-group queue, selections and offset/limit budget, as
255    /// they were when the plan was made.
256    frontier: RowGroupFrontier,
257    /// The row group that the decoder was fetching when the plan was made.
258    /// It is planned first.
259    active_row_group: Option<NextRowGroup>,
260    /// The decoder's projection, predicates and batch size.
261    columns: Arc<StageColumns>,
262    /// Planned rows before the next row group.
263    next_row: u64,
264    /// The row group being planned, if any.
265    current: Option<RowGroupRanges>,
266    /// Whether planning has ended.
267    done: bool,
268}
269
270/// The decoder's batch size and decoding stages.
271#[derive(Debug)]
272struct StageColumns {
273    /// The output batch size, which aligns cached predicate reads.
274    batch_size: usize,
275    /// What each decoding stage fetches, shared with the decoder.
276    stages: Arc<StageSchedule>,
277}
278
279/// Builds a [`ScanPlan`] from a decoder's row-group frontier and the columns
280/// each decoding stage reads.
281#[derive(Debug)]
282pub(crate) struct ScanPlanBuilder {
283    frontier: RowGroupFrontier,
284    active_row_group: Option<NextRowGroup>,
285    columns: StageColumns,
286}
287
288impl ScanPlanBuilder {
289    /// Plan the row groups in `frontier`, decoding in batches of
290    /// `batch_size` rows with the decoding stages `stages`.
291    pub(crate) fn new(
292        frontier: RowGroupFrontier,
293        batch_size: usize,
294        stages: Arc<StageSchedule>,
295    ) -> Self {
296        Self {
297            frontier,
298            active_row_group: None,
299            columns: StageColumns { batch_size, stages },
300        }
301    }
302
303    /// Plan `active_row_group` first: the row group that the decoder is
304    /// fetching, which the frontier has already handed over.
305    pub(crate) fn with_active_row_group(mut self, active_row_group: Option<NextRowGroup>) -> Self {
306        self.active_row_group = active_row_group;
307        self
308    }
309
310    pub(crate) fn build(self) -> ScanPlan {
311        let Self {
312            frontier,
313            active_row_group,
314            columns,
315        } = self;
316        let planner = Planner {
317            frontier,
318            active_row_group,
319            columns: Arc::new(columns),
320            next_row: 0,
321            current: None,
322            done: false,
323        };
324        ScanPlan {
325            planner: Some(Box::new(planner)),
326        }
327    }
328}
329
330impl Planner {
331    /// Start planning the next row group the decoder will read.
332    ///
333    /// Returns `Ok(false)` when no row group remains. A row group that the
334    /// offset/limit budget removes is planned with no ranges.
335    fn plan_next_row_group(&mut self) -> Result<bool, ParquetError> {
336        // Use the decoder's own row-group walk, so row groups that the
337        // selection or the budget removes are skipped here too.
338        let next_row_group = match self.active_row_group.take() {
339            Some(active) => Some(active),
340            None => self.frontier.next_readable_row_group()?,
341        };
342        let Some(NextRowGroup {
343            row_group_idx,
344            row_count,
345            selection,
346            budget,
347        }) = next_row_group
348        else {
349            return Ok(false);
350        };
351
352        let filtered = self.frontier.has_predicates;
353        let selection = if filtered {
354            // Predicates see every selected row. Offset and limit apply to
355            // their output, so they cannot be applied before decoding.
356            selection
357        } else {
358            // Apply offset and limit as the decoder does before it requests
359            // the output columns.
360            let plan_builder =
361                ReadPlanBuilder::new(self.columns.batch_size).with_selection(selection);
362            let BudgetedReadPlan {
363                plan_builder,
364                rows_after_budget,
365                remaining_budget,
366                ..
367            } = budget.apply_to_plan(plan_builder, row_count);
368            self.frontier
369                .update_budget_after_row_group(remaining_budget);
370            if rows_after_budget == 0 {
371                self.current = None;
372                return Ok(true);
373            }
374            plan_builder.selection().cloned()
375        };
376
377        let rows = SelectedRows::new(selection.as_ref(), row_count);
378        let first_row = self.next_row;
379        self.next_row += rows.selected_before(row_count);
380
381        let metadata = &self.frontier.parquet_metadata;
382        let page_index = metadata
383            .page_index()
384            .filter(|page_index| page_index.has_offset_indexes())
385            .cloned();
386        let row_group = metadata.row_group(row_group_idx);
387
388        // Predicate columns that are cached for the output are fetched with the
389        // selection expanded to batch boundaries. See `fetch_ranges`.
390        let stages = &self.columns.stages;
391        let expanded_selection = match (&selection, stages.cache_projection()) {
392            (Some(selection), Some(_)) => {
393                Some(selection.expand_to_batch_boundaries(self.columns.batch_size, row_count))
394            }
395            _ => None,
396        };
397
398        let num_columns = row_group.columns().len();
399        let mut planned_columns = vec![false; num_columns];
400        let mut stage_plans = vec![];
401        for (stage, fetch) in stages.stages() {
402            // The decoder reuses a column that an earlier stage read.
403            // `columns_to_fetch` filters, so it gives no size hint. Reserve
404            // for every column to avoid growing the vector one column at a time.
405            let mut columns = Vec::with_capacity(num_columns);
406            columns.extend(columns_to_fetch(fetch.projection, num_columns, |idx| {
407                planned_columns[idx]
408            }));
409            // The plan keeps `columns` until the end of the row group. Release
410            // the capacity that a narrow projection does not use.
411            columns.shrink_to_fit();
412            for &column_idx in &columns {
413                planned_columns[column_idx] = true;
414            }
415            stage_plans.push(StagePlan { stage, columns });
416        }
417
418        self.current = Some(RowGroupRanges {
419            row_group: RowGroupContext {
420                row_group_idx,
421                row_count,
422                first_row,
423                rows,
424                selection,
425                expanded_selection,
426                stages: Arc::clone(&self.columns.stages),
427                metadata: Arc::clone(metadata),
428                page_index,
429            },
430            stages: stage_plans.into_iter(),
431            stage: None,
432        });
433        Ok(true)
434    }
435}
436
437impl Iterator for ScanPlan {
438    type Item = PlannedRange;
439
440    fn next(&mut self) -> Option<PlannedRange> {
441        self.planner.as_mut()?.next()
442    }
443}
444
445impl Planner {
446    fn next(&mut self) -> Option<PlannedRange> {
447        loop {
448            if let Some(range) = self.current.as_mut().and_then(RowGroupRanges::next) {
449                return Some(range);
450            }
451            if self.done {
452                return None;
453            }
454            match self.plan_next_row_group() {
455                Ok(true) => {}
456                // The decoder reports the same error when it reaches this row
457                // group. The plan is advisory, so it ends here.
458                Ok(false) | Err(_) => {
459                    self.current = None;
460                    self.done = true;
461                }
462            }
463        }
464    }
465}
466
467/// Once [`ScanPlan::next`] returns `None`, it always returns `None`.
468impl FusedIterator for ScanPlan {}
469
470/// The columns that one decoding stage of a row group reads first.
471#[derive(Debug, Clone)]
472struct StagePlan {
473    stage: ScanStage,
474    /// Leaf column indexes, in file order.
475    columns: Vec<usize>,
476}
477
478/// The planned ranges of one row group, in decode order.
479///
480/// Each stage is a merge of one cursor per column chunk. A cursor
481/// returns the ranges of its column chunk in file order, and their first
482/// rows do not decrease. A heap picks the cursor whose next range has the
483/// smallest [`RangeOrder`], so the ranges of a stage are ordered by first
484/// row without collecting and sorting all of them.
485#[derive(Debug, Clone)]
486struct RowGroupRanges {
487    row_group: RowGroupContext,
488    /// Stages not yet started.
489    stages: std::vec::IntoIter<StagePlan>,
490    /// The stage being merged.
491    stage: Option<StageRanges>,
492}
493
494/// What the ranges of one row group are computed from.
495#[derive(Debug, Clone)]
496struct RowGroupContext {
497    row_group_idx: usize,
498    row_count: usize,
499    /// Planned rows before this row group.
500    first_row: u64,
501    rows: SelectedRows,
502    /// The selection the decoder uses to choose pages.
503    selection: Option<RowSelection>,
504    /// The selection expanded to batch boundaries, for cached predicate
505    /// columns.
506    expanded_selection: Option<RowSelection>,
507    /// What each decoding stage fetches.
508    stages: Arc<StageSchedule>,
509    /// The file metadata, for the byte ranges of the column chunks.
510    metadata: Arc<ParquetMetaData>,
511    /// The file's page index, if it has offset indexes.
512    page_index: Option<Arc<dyn PageIndexProvider>>,
513}
514
515/// The merge state of one stage.
516///
517/// A cursor of a column chunk is one heap entry: the sort key of its next
518/// range, the column and the position of that range. The other parts of the
519/// range are computed again when the entry is popped. Thus a stage stores
520/// no per-column state other than its heap entry, unless the selection
521/// removes pages.
522#[derive(Debug, Clone)]
523struct StageRanges {
524    /// Leaf column indexes, in file order. A [`HeapEntry`] refers to a
525    /// column by its position in this list.
526    columns: Vec<usize>,
527    /// For each column in `columns`, the indexes of the data pages that the
528    /// decoder reads, if the selection removes pages. `None` if no column of
529    /// the stage has such a list.
530    selected: Option<Vec<Option<Vec<usize>>>>,
531    /// The next range of each cursor that has one, smallest first.
532    heap: BinaryHeap<Reverse<HeapEntry>>,
533}
534
535/// How the heap orders the next ranges of different column chunks: by first
536/// row, then more rows first (a dictionary page), then by file offset. The
537/// ranges of one column chunk keep file order, also a page with no planned
538/// rows that the predicate cache reads.
539///
540/// The key is `(first row, Reverse(last row), range start)`.
541type RangeOrder = (u64, Reverse<u64>, u64);
542
543/// A heap entry: the sort key of the next range of a cursor, the position
544/// of the column in [`StageRanges::columns`], and the [`HeadPosition`] of
545/// that range. The column position breaks ties, so ranges with equal keys
546/// stay in file order of their columns.
547type HeapEntry = (RangeOrder, u32, HeadPosition);
548
549/// Which range of a column chunk is next: [`CHUNK`], [`DICTIONARY`], or the
550/// position of a data page in the pages that the decoder reads.
551type HeadPosition = u32;
552
553/// The next range is the whole column chunk. The column has no offset index.
554const CHUNK: HeadPosition = u32::MAX;
555
556/// The next range is the dictionary page.
557const DICTIONARY: HeadPosition = u32::MAX - 1;
558
559/// Converts a column or page position to a heap entry field.
560fn position(position: usize) -> u32 {
561    u32::try_from(position).expect("fewer than u32::MAX - 1 columns and pages")
562}
563
564/// The data pages of a column chunk that the decoder reads, in page order.
565#[derive(Debug, Clone, Copy)]
566enum PageSet<'a> {
567    /// Every page of the offset index, which has this many pages.
568    All(usize),
569    /// The pages at these indexes in the offset index.
570    Selected(&'a [usize]),
571}
572
573impl PageSet<'_> {
574    fn len(&self) -> usize {
575        match self {
576            Self::All(len) => *len,
577            Self::Selected(pages) => pages.len(),
578        }
579    }
580
581    /// Index in the offset index of the page at `position`.
582    fn page(&self, position: usize) -> usize {
583        match self {
584            Self::All(_) => position,
585            Self::Selected(pages) => pages[position],
586        }
587    }
588}
589
590impl StageRanges {
591    /// The data pages that the decoder reads of the column at `slot`, whose
592    /// page locations are `locations`.
593    fn pages(&self, slot: usize, locations: &[PageLocation]) -> PageSet<'_> {
594        match self
595            .selected
596            .as_ref()
597            .and_then(|selected| selected[slot].as_ref())
598        {
599            Some(pages) => PageSet::Selected(pages),
600            None => PageSet::All(locations.len()),
601        }
602    }
603}
604
605impl RowGroupRanges {
606    fn next(&mut self) -> Option<PlannedRange> {
607        loop {
608            if let Some(stage) = self.stage.as_mut()
609                && let Some(Reverse(((first_row, Reverse(last_row), start), slot, head))) =
610                    stage.heap.pop()
611            {
612                let row_group = &self.row_group;
613                let column_idx = stage.columns[slot as usize];
614                let (end, kind) = if head == CHUNK {
615                    (row_group.chunk(column_idx).end, PageKind::ColumnChunk)
616                } else {
617                    let locations = row_group
618                        .locations(column_idx)
619                        .expect("pages have locations");
620                    let pages = stage.pages(slot as usize, locations);
621                    let (end, kind, next) = if head == DICTIONARY {
622                        (locations[0].offset as u64, PageKind::Dictionary, 0)
623                    } else {
624                        let page = page_range(&locations[pages.page(head as usize)]);
625                        (page.end, PageKind::Data, head as usize + 1)
626                    };
627                    if let Some(order) = row_group.data_page(locations, pages, next) {
628                        stage.heap.push(Reverse((order, slot, position(next))));
629                    }
630                    (end, kind)
631                };
632                return Some(PlannedRange {
633                    range: start..end,
634                    row_group: row_group.row_group_idx,
635                    column: column_idx,
636                    kind,
637                    first_row,
638                    last_row,
639                });
640            }
641            let plan = self.stages.next()?;
642            let mut selected = None;
643            let mut heap = Vec::with_capacity(plan.columns.len());
644            for (slot, &column_idx) in plan.columns.iter().enumerate() {
645                let (head, pages) = self.row_group.first_range(column_idx, plan.stage);
646                if let Some(pages) = pages {
647                    selected.get_or_insert_with(|| vec![None; plan.columns.len()])[slot] =
648                        Some(pages);
649                }
650                if let Some((order, head)) = head {
651                    heap.push(Reverse((order, position(slot), head)));
652                }
653            }
654            self.stage = Some(StageRanges {
655                columns: plan.columns,
656                selected,
657                heap: BinaryHeap::from(heap),
658            });
659        }
660    }
661}
662
663impl RowGroupContext {
664    fn locations(&self, column_idx: usize) -> Option<&[PageLocation]> {
665        self.page_index
666            .as_ref()?
667            .offset_index(self.row_group_idx, column_idx)
668            .map(|offset_index| offset_index.page_locations().as_slice())
669    }
670
671    /// Byte range of the whole column chunk.
672    fn chunk(&self, column_idx: usize) -> Range<u64> {
673        let (start, len) = self
674            .metadata
675            .row_group(self.row_group_idx)
676            .column(column_idx)
677            .byte_range();
678        start..start + len
679    }
680
681    /// Planned rows of the whole row group.
682    fn row_group_rows(&self) -> Range<u64> {
683        self.first_row..self.first_row + self.rows.selected_before(self.row_count)
684    }
685
686    /// Planned rows of the data page at `idx` in `locations`.
687    fn page_rows(&self, locations: &[PageLocation], idx: usize) -> Range<u64> {
688        let raw_end = locations
689            .get(idx + 1)
690            .map(|next| next.first_row_index as usize)
691            .unwrap_or(self.row_count);
692        let first_row = self.first_row
693            + self
694                .rows
695                .selected_before(locations[idx].first_row_index as usize);
696        first_row..self.first_row + self.rows.selected_before(raw_end)
697    }
698
699    /// The sort key of the data page at `position` in `pages`, if any.
700    fn data_page(
701        &self,
702        locations: &[PageLocation],
703        pages: PageSet<'_>,
704        position: usize,
705    ) -> Option<RangeOrder> {
706        if position >= pages.len() {
707            return None;
708        }
709        let idx = pages.page(position);
710        let rows = self.page_rows(locations, idx);
711        Some((rows.start, Reverse(rows.end), locations[idx].offset as u64))
712    }
713
714    /// The first range of one column chunk: its sort key and position, or
715    /// `None` if the column chunk has no range. Also returns the data pages
716    /// that the decoder reads, if the selection removes pages.
717    ///
718    /// The ranges are the same bytes that `InMemoryRowGroup::fetch_ranges`
719    /// requests, split at page boundaries when page locations are known.
720    fn first_range(
721        &self,
722        column_idx: usize,
723        stage: ScanStage,
724    ) -> (Option<(RangeOrder, HeadPosition)>, Option<Vec<usize>>) {
725        let fetch_selection = column_selection(
726            self.selection.as_ref(),
727            self.expanded_selection.as_ref(),
728            self.stages.fetch(stage).cache_projection,
729            column_idx,
730        );
731        let all_locations = self.locations(column_idx);
732        let fetch = ColumnFetch::new(self.chunk(column_idx), all_locations, fetch_selection);
733        // The decoder fetches a whole column chunk as one range. If page
734        // locations are known, the plan splits it into the same bytes, page
735        // by page.
736        let locations = all_locations.filter(|l| !l.is_empty());
737        let (locations, dictionary, selected) = match (fetch, locations) {
738            (ColumnFetch::Chunk { range }, Some(locations)) => {
739                (locations, dictionary_range(range.start, locations), None)
740            }
741            (ColumnFetch::Chunk { range }, None) => {
742                let rows = self.row_group_rows();
743                let order = (rows.start, Reverse(rows.end), range.start);
744                return (Some((order, CHUNK)), None);
745            }
746            (
747                ColumnFetch::Pages {
748                    dictionary,
749                    locations,
750                    pages,
751                },
752                _,
753            ) => (locations, dictionary, Some(pages)),
754        };
755        let pages = match &selected {
756            Some(pages) => PageSet::Selected(pages),
757            None => PageSet::All(locations.len()),
758        };
759
760        let head = if let Some(dictionary) = dictionary {
761            // The dictionary serves exactly the rows of the data pages read.
762            let rows = match pages.len() {
763                0 => self.row_group_rows(),
764                len => {
765                    let first = self.page_rows(locations, pages.page(0));
766                    let last = self.page_rows(locations, pages.page(len - 1));
767                    first.start..last.end
768                }
769            };
770            Some((
771                (rows.start, Reverse(rows.end), dictionary.start),
772                DICTIONARY,
773            ))
774        } else {
775            self.data_page(locations, pages, 0).map(|order| (order, 0))
776        };
777        (head, selected)
778    }
779}
780
781/// Counts the selected rows before a position in a row group.
782#[derive(Debug, Clone)]
783struct SelectedRows {
784    /// `(first raw row, selected rows before it, selected)` per selector.
785    /// `None` when every row is selected.
786    runs: Option<Vec<(usize, u64, bool)>>,
787}
788
789impl SelectedRows {
790    fn new(selection: Option<&RowSelection>, row_count: usize) -> Self {
791        let runs = selection.map(|selection| {
792            let mut runs = Vec::new();
793            let mut raw = 0;
794            let mut selected = 0;
795            for selector in selection.iter() {
796                if selector.row_count == 0 {
797                    continue;
798                }
799                runs.push((raw, selected, !selector.skip));
800                raw += selector.row_count;
801                if !selector.skip {
802                    selected += selector.row_count as u64;
803                }
804            }
805            // A selection shorter than the row group skips the trailing rows.
806            if raw < row_count {
807                runs.push((raw, selected, false));
808            }
809            runs
810        });
811        Self { runs }
812    }
813
814    /// The number of selected rows in `0..raw`.
815    fn selected_before(&self, raw: usize) -> u64 {
816        let Some(runs) = &self.runs else {
817            return raw as u64;
818        };
819        let idx = runs.partition_point(|(start, _, _)| *start < raw);
820        let Some(&(start, selected_before, selected)) = idx.checked_sub(1).map(|idx| &runs[idx])
821        else {
822            return 0;
823        };
824        if selected {
825            selected_before + (raw - start) as u64
826        } else {
827            selected_before
828        }
829    }
830}
831
832#[cfg(test)]
833mod tests;