parquet/arrow/push_decoder/scan_plan/
budget.rs1use crate::arrow::arrow_reader::ReadPlanBuilder;
21
22#[derive(Debug, Clone, Copy, Eq, PartialEq)]
24pub(crate) struct RowBudget {
25 offset: Option<usize>,
26 limit: Option<usize>,
27}
28
29impl RowBudget {
30 pub(crate) fn new(offset: Option<usize>, limit: Option<usize>) -> Self {
31 Self { offset, limit }
32 }
33
34 pub(crate) fn is_exhausted(self) -> bool {
35 matches!(self.limit, Some(0))
36 }
37
38 pub(crate) fn offset(self) -> Option<usize> {
40 self.offset
41 }
42
43 pub(crate) fn limit(self) -> Option<usize> {
45 self.limit
46 }
47
48 pub(crate) fn rows_after(self, rows_before_budget: usize) -> usize {
50 let rows_after_offset = rows_before_budget.saturating_sub(self.offset.unwrap_or(0));
51 match self.limit {
52 Some(limit) => rows_after_offset.min(limit),
53 None => rows_after_offset,
54 }
55 }
56
57 pub(crate) fn selected_row_limit(self) -> Option<usize> {
59 self.limit
60 .map(|limit| limit.saturating_add(self.offset.unwrap_or(0)))
61 }
62
63 pub(crate) fn apply_to_plan(
64 self,
65 plan_builder: ReadPlanBuilder,
66 row_count: usize,
67 ) -> BudgetedReadPlan {
68 let rows_before_budget = plan_builder.num_rows_selected().unwrap_or(row_count);
69 let plan_builder = plan_builder
70 .limited(row_count)
71 .with_offset(self.offset)
72 .with_limit(self.limit)
73 .build_limited();
74 let rows_after_budget = self.rows_after(rows_before_budget);
75
76 BudgetedReadPlan {
77 plan_builder,
78 rows_before_budget,
79 rows_after_budget,
80 remaining_budget: self.advance(rows_before_budget, rows_after_budget),
81 }
82 }
83
84 pub(crate) fn advance(mut self, rows_before_budget: usize, rows_after_budget: usize) -> Self {
90 if let Some(offset) = &mut self.offset {
91 *offset = offset.saturating_sub(rows_before_budget - rows_after_budget);
94 }
95
96 if rows_after_budget != 0
97 && let Some(limit) = &mut self.limit
98 {
99 *limit -= rows_after_budget;
100 }
101
102 self
103 }
104}
105
106#[derive(Debug)]
107pub(crate) struct BudgetedReadPlan {
108 pub(crate) plan_builder: ReadPlanBuilder,
110 pub(crate) rows_before_budget: usize,
113 pub(crate) rows_after_budget: usize,
116 pub(crate) remaining_budget: RowBudget,
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123 use crate::arrow::arrow_reader::{RowSelection, RowSelector};
124
125 #[test]
126 fn test_row_budget_offset_limit_across_row_groups() {
127 let first =
128 RowBudget::new(Some(225), Some(20)).apply_to_plan(ReadPlanBuilder::new(1024), 200);
129 assert_eq!(first.rows_before_budget, 200);
130 assert_eq!(first.rows_after_budget, 0);
131 assert_eq!(first.remaining_budget, RowBudget::new(Some(25), Some(20)));
132 assert_eq!(first.plan_builder.num_rows_selected(), Some(0));
133
134 let second = first
135 .remaining_budget
136 .apply_to_plan(ReadPlanBuilder::new(1024), 200);
137 assert_eq!(second.rows_before_budget, 200);
138 assert_eq!(second.rows_after_budget, 20);
139 assert_eq!(second.remaining_budget, RowBudget::new(Some(0), Some(0)));
140 assert_eq!(second.plan_builder.num_rows_selected(), Some(20));
141 }
142
143 #[test]
144 fn test_row_budget_limit_only() {
145 let budgeted =
146 RowBudget::new(None, Some(20)).apply_to_plan(ReadPlanBuilder::new(1024), 200);
147 assert_eq!(budgeted.rows_before_budget, 200);
148 assert_eq!(budgeted.rows_after_budget, 20);
149 assert_eq!(budgeted.remaining_budget, RowBudget::new(None, Some(0)));
150 assert_eq!(budgeted.plan_builder.num_rows_selected(), Some(20));
151 }
152
153 #[test]
154 fn test_row_budget_empty_selection() {
155 let empty_selection = RowSelection::from(vec![RowSelector::skip(200)]);
156 let budgeted = RowBudget::new(Some(10), Some(20)).apply_to_plan(
157 ReadPlanBuilder::new(1024).with_selection(Some(empty_selection)),
158 200,
159 );
160 assert_eq!(budgeted.rows_before_budget, 0);
161 assert_eq!(budgeted.rows_after_budget, 0);
162 assert_eq!(
163 budgeted.remaining_budget,
164 RowBudget::new(Some(10), Some(20))
165 );
166 assert_eq!(budgeted.plan_builder.num_rows_selected(), Some(0));
167 }
168}