use crate::arrow::arrow_reader::ReadPlanBuilder;
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub(crate) struct RowBudget {
offset: Option<usize>,
limit: Option<usize>,
}
impl RowBudget {
pub(crate) fn new(offset: Option<usize>, limit: Option<usize>) -> Self {
Self { offset, limit }
}
pub(crate) fn is_exhausted(self) -> bool {
matches!(self.limit, Some(0))
}
pub(crate) fn offset(self) -> Option<usize> {
self.offset
}
pub(crate) fn limit(self) -> Option<usize> {
self.limit
}
pub(crate) fn rows_after(self, rows_before_budget: usize) -> usize {
let rows_after_offset = rows_before_budget.saturating_sub(self.offset.unwrap_or(0));
match self.limit {
Some(limit) => rows_after_offset.min(limit),
None => rows_after_offset,
}
}
pub(crate) fn selected_row_limit(self) -> Option<usize> {
self.limit
.map(|limit| limit.saturating_add(self.offset.unwrap_or(0)))
}
pub(crate) fn apply_to_plan(
self,
plan_builder: ReadPlanBuilder,
row_count: usize,
) -> BudgetedReadPlan {
let rows_before_budget = plan_builder.num_rows_selected().unwrap_or(row_count);
let plan_builder = plan_builder
.limited(row_count)
.with_offset(self.offset)
.with_limit(self.limit)
.build_limited();
let rows_after_budget = self.rows_after(rows_before_budget);
BudgetedReadPlan {
plan_builder,
rows_before_budget,
rows_after_budget,
remaining_budget: self.advance(rows_before_budget, rows_after_budget),
}
}
pub(crate) fn advance(mut self, rows_before_budget: usize, rows_after_budget: usize) -> Self {
if let Some(offset) = &mut self.offset {
*offset = offset.saturating_sub(rows_before_budget - rows_after_budget);
}
if rows_after_budget != 0
&& let Some(limit) = &mut self.limit
{
*limit -= rows_after_budget;
}
self
}
}
#[derive(Debug)]
pub(crate) struct BudgetedReadPlan {
pub(crate) plan_builder: ReadPlanBuilder,
pub(crate) rows_before_budget: usize,
pub(crate) rows_after_budget: usize,
pub(crate) remaining_budget: RowBudget,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::arrow::arrow_reader::{RowSelection, RowSelector};
#[test]
fn test_row_budget_offset_limit_across_row_groups() {
let first =
RowBudget::new(Some(225), Some(20)).apply_to_plan(ReadPlanBuilder::new(1024), 200);
assert_eq!(first.rows_before_budget, 200);
assert_eq!(first.rows_after_budget, 0);
assert_eq!(first.remaining_budget, RowBudget::new(Some(25), Some(20)));
assert_eq!(first.plan_builder.num_rows_selected(), Some(0));
let second = first
.remaining_budget
.apply_to_plan(ReadPlanBuilder::new(1024), 200);
assert_eq!(second.rows_before_budget, 200);
assert_eq!(second.rows_after_budget, 20);
assert_eq!(second.remaining_budget, RowBudget::new(Some(0), Some(0)));
assert_eq!(second.plan_builder.num_rows_selected(), Some(20));
}
#[test]
fn test_row_budget_limit_only() {
let budgeted =
RowBudget::new(None, Some(20)).apply_to_plan(ReadPlanBuilder::new(1024), 200);
assert_eq!(budgeted.rows_before_budget, 200);
assert_eq!(budgeted.rows_after_budget, 20);
assert_eq!(budgeted.remaining_budget, RowBudget::new(None, Some(0)));
assert_eq!(budgeted.plan_builder.num_rows_selected(), Some(20));
}
#[test]
fn test_row_budget_empty_selection() {
let empty_selection = RowSelection::from(vec![RowSelector::skip(200)]);
let budgeted = RowBudget::new(Some(10), Some(20)).apply_to_plan(
ReadPlanBuilder::new(1024).with_selection(Some(empty_selection)),
200,
);
assert_eq!(budgeted.rows_before_budget, 0);
assert_eq!(budgeted.rows_after_budget, 0);
assert_eq!(
budgeted.remaining_budget,
RowBudget::new(Some(10), Some(20))
);
assert_eq!(budgeted.plan_builder.num_rows_selected(), Some(0));
}
}