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;