use std::cmp::Reverse;
use std::collections::BinaryHeap;
use std::iter::FusedIterator;
use std::ops::Range;
use std::sync::Arc;
use super::reader_builder::StageSchedule;
use crate::arrow::arrow_reader::{ReadPlanBuilder, RowSelection};
use crate::arrow::in_memory_row_group::{
ColumnFetch, column_selection, columns_to_fetch, dictionary_range, page_range,
};
use crate::errors::ParquetError;
use crate::file::metadata::ParquetMetaData;
use crate::file::metadata::page_index::PageIndexProvider;
use crate::file::page_index::offset_index::PageLocation;
mod budget;
mod frontier;
pub(crate) use budget::{BudgetedReadPlan, RowBudget};
pub(crate) use frontier::{NextRowGroup, RowGroupFrontier};
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct PlannedRange {
pub range: Range<u64>,
pub row_group: usize,
pub column: usize,
pub kind: PageKind,
#[cfg_attr(not(test), expect(dead_code))]
pub(crate) first_row: u64,
#[cfg_attr(not(test), expect(dead_code))]
pub(crate) last_row: u64,
}
impl PartialEq for PlannedRange {
fn eq(&self, other: &Self) -> bool {
let Self {
range,
row_group,
column,
kind,
first_row: _,
last_row: _,
} = self;
*range == other.range
&& *row_group == other.row_group
&& *column == other.column
&& *kind == other.kind
}
}
impl Eq for PlannedRange {}
impl PlannedRange {
pub fn len(&self) -> u64 {
self.range.end - self.range.start
}
pub fn is_empty(&self) -> bool {
self.range.is_empty()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum PageKind {
Dictionary,
Data,
ColumnChunk,
}
pub(crate) use super::reader_builder::Stage as ScanStage;
#[derive(Debug, Clone)]
pub struct ScanPlan {
planner: Option<Box<Planner>>,
}
impl ScanPlan {
pub(crate) fn empty() -> Self {
Self { planner: None }
}
}
#[derive(Debug, Clone)]
struct Planner {
frontier: RowGroupFrontier,
active_row_group: Option<NextRowGroup>,
columns: Arc<StageColumns>,
next_row: u64,
current: Option<RowGroupRanges>,
done: bool,
}
#[derive(Debug)]
struct StageColumns {
batch_size: usize,
stages: Arc<StageSchedule>,
}
#[derive(Debug)]
pub(crate) struct ScanPlanBuilder {
frontier: RowGroupFrontier,
active_row_group: Option<NextRowGroup>,
columns: StageColumns,
}
impl ScanPlanBuilder {
pub(crate) fn new(
frontier: RowGroupFrontier,
batch_size: usize,
stages: Arc<StageSchedule>,
) -> Self {
Self {
frontier,
active_row_group: None,
columns: StageColumns { batch_size, stages },
}
}
pub(crate) fn with_active_row_group(mut self, active_row_group: Option<NextRowGroup>) -> Self {
self.active_row_group = active_row_group;
self
}
pub(crate) fn build(self) -> ScanPlan {
let Self {
frontier,
active_row_group,
columns,
} = self;
let planner = Planner {
frontier,
active_row_group,
columns: Arc::new(columns),
next_row: 0,
current: None,
done: false,
};
ScanPlan {
planner: Some(Box::new(planner)),
}
}
}
impl Planner {
fn plan_next_row_group(&mut self) -> Result<bool, ParquetError> {
let next_row_group = match self.active_row_group.take() {
Some(active) => Some(active),
None => self.frontier.next_readable_row_group()?,
};
let Some(NextRowGroup {
row_group_idx,
row_count,
selection,
budget,
}) = next_row_group
else {
return Ok(false);
};
let filtered = self.frontier.has_predicates;
let selection = if filtered {
selection
} else {
let plan_builder =
ReadPlanBuilder::new(self.columns.batch_size).with_selection(selection);
let BudgetedReadPlan {
plan_builder,
rows_after_budget,
remaining_budget,
..
} = budget.apply_to_plan(plan_builder, row_count);
self.frontier
.update_budget_after_row_group(remaining_budget);
if rows_after_budget == 0 {
self.current = None;
return Ok(true);
}
plan_builder.selection().cloned()
};
let rows = SelectedRows::new(selection.as_ref(), row_count);
let first_row = self.next_row;
self.next_row += rows.selected_before(row_count);
let metadata = &self.frontier.parquet_metadata;
let page_index = metadata
.page_index()
.filter(|page_index| page_index.has_offset_indexes())
.cloned();
let row_group = metadata.row_group(row_group_idx);
let stages = &self.columns.stages;
let expanded_selection = match (&selection, stages.cache_projection()) {
(Some(selection), Some(_)) => {
Some(selection.expand_to_batch_boundaries(self.columns.batch_size, row_count))
}
_ => None,
};
let num_columns = row_group.columns().len();
let mut planned_columns = vec![false; num_columns];
let mut stage_plans = vec![];
for (stage, fetch) in stages.stages() {
let mut columns = Vec::with_capacity(num_columns);
columns.extend(columns_to_fetch(fetch.projection, num_columns, |idx| {
planned_columns[idx]
}));
columns.shrink_to_fit();
for &column_idx in &columns {
planned_columns[column_idx] = true;
}
stage_plans.push(StagePlan { stage, columns });
}
self.current = Some(RowGroupRanges {
row_group: RowGroupContext {
row_group_idx,
row_count,
first_row,
rows,
selection,
expanded_selection,
stages: Arc::clone(&self.columns.stages),
metadata: Arc::clone(metadata),
page_index,
},
stages: stage_plans.into_iter(),
stage: None,
});
Ok(true)
}
}
impl Iterator for ScanPlan {
type Item = PlannedRange;
fn next(&mut self) -> Option<PlannedRange> {
self.planner.as_mut()?.next()
}
}
impl Planner {
fn next(&mut self) -> Option<PlannedRange> {
loop {
if let Some(range) = self.current.as_mut().and_then(RowGroupRanges::next) {
return Some(range);
}
if self.done {
return None;
}
match self.plan_next_row_group() {
Ok(true) => {}
Ok(false) | Err(_) => {
self.current = None;
self.done = true;
}
}
}
}
}
impl FusedIterator for ScanPlan {}
#[derive(Debug, Clone)]
struct StagePlan {
stage: ScanStage,
columns: Vec<usize>,
}
#[derive(Debug, Clone)]
struct RowGroupRanges {
row_group: RowGroupContext,
stages: std::vec::IntoIter<StagePlan>,
stage: Option<StageRanges>,
}
#[derive(Debug, Clone)]
struct RowGroupContext {
row_group_idx: usize,
row_count: usize,
first_row: u64,
rows: SelectedRows,
selection: Option<RowSelection>,
expanded_selection: Option<RowSelection>,
stages: Arc<StageSchedule>,
metadata: Arc<ParquetMetaData>,
page_index: Option<Arc<dyn PageIndexProvider>>,
}
#[derive(Debug, Clone)]
struct StageRanges {
columns: Vec<usize>,
selected: Option<Vec<Option<Vec<usize>>>>,
heap: BinaryHeap<Reverse<HeapEntry>>,
}
type RangeOrder = (u64, Reverse<u64>, u64);
type HeapEntry = (RangeOrder, u32, HeadPosition);
type HeadPosition = u32;
const CHUNK: HeadPosition = u32::MAX;
const DICTIONARY: HeadPosition = u32::MAX - 1;
fn position(position: usize) -> u32 {
u32::try_from(position).expect("fewer than u32::MAX - 1 columns and pages")
}
#[derive(Debug, Clone, Copy)]
enum PageSet<'a> {
All(usize),
Selected(&'a [usize]),
}
impl PageSet<'_> {
fn len(&self) -> usize {
match self {
Self::All(len) => *len,
Self::Selected(pages) => pages.len(),
}
}
fn page(&self, position: usize) -> usize {
match self {
Self::All(_) => position,
Self::Selected(pages) => pages[position],
}
}
}
impl StageRanges {
fn pages(&self, slot: usize, locations: &[PageLocation]) -> PageSet<'_> {
match self
.selected
.as_ref()
.and_then(|selected| selected[slot].as_ref())
{
Some(pages) => PageSet::Selected(pages),
None => PageSet::All(locations.len()),
}
}
}
impl RowGroupRanges {
fn next(&mut self) -> Option<PlannedRange> {
loop {
if let Some(stage) = self.stage.as_mut()
&& let Some(Reverse(((first_row, Reverse(last_row), start), slot, head))) =
stage.heap.pop()
{
let row_group = &self.row_group;
let column_idx = stage.columns[slot as usize];
let (end, kind) = if head == CHUNK {
(row_group.chunk(column_idx).end, PageKind::ColumnChunk)
} else {
let locations = row_group
.locations(column_idx)
.expect("pages have locations");
let pages = stage.pages(slot as usize, locations);
let (end, kind, next) = if head == DICTIONARY {
(locations[0].offset as u64, PageKind::Dictionary, 0)
} else {
let page = page_range(&locations[pages.page(head as usize)]);
(page.end, PageKind::Data, head as usize + 1)
};
if let Some(order) = row_group.data_page(locations, pages, next) {
stage.heap.push(Reverse((order, slot, position(next))));
}
(end, kind)
};
return Some(PlannedRange {
range: start..end,
row_group: row_group.row_group_idx,
column: column_idx,
kind,
first_row,
last_row,
});
}
let plan = self.stages.next()?;
let mut selected = None;
let mut heap = Vec::with_capacity(plan.columns.len());
for (slot, &column_idx) in plan.columns.iter().enumerate() {
let (head, pages) = self.row_group.first_range(column_idx, plan.stage);
if let Some(pages) = pages {
selected.get_or_insert_with(|| vec![None; plan.columns.len()])[slot] =
Some(pages);
}
if let Some((order, head)) = head {
heap.push(Reverse((order, position(slot), head)));
}
}
self.stage = Some(StageRanges {
columns: plan.columns,
selected,
heap: BinaryHeap::from(heap),
});
}
}
}
impl RowGroupContext {
fn locations(&self, column_idx: usize) -> Option<&[PageLocation]> {
self.page_index
.as_ref()?
.offset_index(self.row_group_idx, column_idx)
.map(|offset_index| offset_index.page_locations().as_slice())
}
fn chunk(&self, column_idx: usize) -> Range<u64> {
let (start, len) = self
.metadata
.row_group(self.row_group_idx)
.column(column_idx)
.byte_range();
start..start + len
}
fn row_group_rows(&self) -> Range<u64> {
self.first_row..self.first_row + self.rows.selected_before(self.row_count)
}
fn page_rows(&self, locations: &[PageLocation], idx: usize) -> Range<u64> {
let raw_end = locations
.get(idx + 1)
.map(|next| next.first_row_index as usize)
.unwrap_or(self.row_count);
let first_row = self.first_row
+ self
.rows
.selected_before(locations[idx].first_row_index as usize);
first_row..self.first_row + self.rows.selected_before(raw_end)
}
fn data_page(
&self,
locations: &[PageLocation],
pages: PageSet<'_>,
position: usize,
) -> Option<RangeOrder> {
if position >= pages.len() {
return None;
}
let idx = pages.page(position);
let rows = self.page_rows(locations, idx);
Some((rows.start, Reverse(rows.end), locations[idx].offset as u64))
}
fn first_range(
&self,
column_idx: usize,
stage: ScanStage,
) -> (Option<(RangeOrder, HeadPosition)>, Option<Vec<usize>>) {
let fetch_selection = column_selection(
self.selection.as_ref(),
self.expanded_selection.as_ref(),
self.stages.fetch(stage).cache_projection,
column_idx,
);
let all_locations = self.locations(column_idx);
let fetch = ColumnFetch::new(self.chunk(column_idx), all_locations, fetch_selection);
let locations = all_locations.filter(|l| !l.is_empty());
let (locations, dictionary, selected) = match (fetch, locations) {
(ColumnFetch::Chunk { range }, Some(locations)) => {
(locations, dictionary_range(range.start, locations), None)
}
(ColumnFetch::Chunk { range }, None) => {
let rows = self.row_group_rows();
let order = (rows.start, Reverse(rows.end), range.start);
return (Some((order, CHUNK)), None);
}
(
ColumnFetch::Pages {
dictionary,
locations,
pages,
},
_,
) => (locations, dictionary, Some(pages)),
};
let pages = match &selected {
Some(pages) => PageSet::Selected(pages),
None => PageSet::All(locations.len()),
};
let head = if let Some(dictionary) = dictionary {
let rows = match pages.len() {
0 => self.row_group_rows(),
len => {
let first = self.page_rows(locations, pages.page(0));
let last = self.page_rows(locations, pages.page(len - 1));
first.start..last.end
}
};
Some((
(rows.start, Reverse(rows.end), dictionary.start),
DICTIONARY,
))
} else {
self.data_page(locations, pages, 0).map(|order| (order, 0))
};
(head, selected)
}
}
#[derive(Debug, Clone)]
struct SelectedRows {
runs: Option<Vec<(usize, u64, bool)>>,
}
impl SelectedRows {
fn new(selection: Option<&RowSelection>, row_count: usize) -> Self {
let runs = selection.map(|selection| {
let mut runs = Vec::new();
let mut raw = 0;
let mut selected = 0;
for selector in selection.iter() {
if selector.row_count == 0 {
continue;
}
runs.push((raw, selected, !selector.skip));
raw += selector.row_count;
if !selector.skip {
selected += selector.row_count as u64;
}
}
if raw < row_count {
runs.push((raw, selected, false));
}
runs
});
Self { runs }
}
fn selected_before(&self, raw: usize) -> u64 {
let Some(runs) = &self.runs else {
return raw as u64;
};
let idx = runs.partition_point(|(start, _, _)| *start < raw);
let Some(&(start, selected_before, selected)) = idx.checked_sub(1).map(|idx| &runs[idx])
else {
return 0;
};
if selected {
selected_before + (raw - start) as u64
} else {
selected_before
}
}
}
#[cfg(test)]
mod tests;