1use crate::error::{Error, ErrorContext, Result};
4
5#[derive(Debug)]
17pub(crate) struct Budget<'scan> {
18 remaining: u64,
19 scan: Option<&'scan mut ScanBudget>,
20}
21
22impl<'scan> Budget<'scan> {
23 pub(crate) fn new(allowance: u64) -> Self {
24 Self {
25 remaining: allowance,
26 scan: None,
27 }
28 }
29
30 pub(crate) fn with_scan(mut self, scan: &'scan mut ScanBudget) -> Self {
31 self.scan = Some(scan);
32 self
33 }
34
35 pub(crate) fn charge_elements(&mut self, count: usize, width: usize) -> Result<()> {
37 let bytes = count.checked_mul(width).ok_or_else(exceeded)?;
38 self.charge(bytes)
39 }
40
41 pub(crate) fn charge(&mut self, bytes: usize) -> Result<()> {
43 let bytes = u64::try_from(bytes).map_err(|_| exceeded())?;
44 if self.remaining < bytes {
45 return Err(exceeded());
46 }
47 if let Some(scan) = self.scan.as_deref_mut() {
49 if scan.remaining_bytes < bytes {
50 return Err(scan_exceeded());
51 }
52 }
53 self.remaining -= bytes;
54 if let Some(scan) = self.scan.as_deref_mut() {
55 scan.remaining_bytes -= bytes;
56 }
57 Ok(())
58 }
59
60 pub(crate) fn record_stream(&mut self, bytes: u64) -> Result<()> {
62 if let Some(scan) = self.scan.as_deref_mut() {
63 scan.record_stream(bytes)?;
64 }
65 Ok(())
66 }
67
68 pub(crate) fn record_bytes_read(&mut self, bytes: u64) -> Result<()> {
71 if let Some(scan) = self.scan.as_deref_mut() {
72 scan.record_bytes_read(bytes)?;
73 }
74 Ok(())
75 }
76}
77
78#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
88pub struct ScanMetrics {
89 blocks_considered: u64,
90 blocks_pruned: u64,
91 streams_decoded: u64,
92 stream_bytes_decoded: u64,
93 bytes_read: u64,
94 rows_returned: u64,
95}
96
97impl ScanMetrics {
98 pub fn blocks_considered(&self) -> u64 {
100 self.blocks_considered
101 }
102
103 pub fn blocks_pruned(&self) -> u64 {
105 self.blocks_pruned
106 }
107
108 pub fn streams_decoded(&self) -> u64 {
110 self.streams_decoded
111 }
112
113 pub fn stream_bytes_decoded(&self) -> u64 {
119 self.stream_bytes_decoded
120 }
121
122 pub fn bytes_read(&self) -> u64 {
130 self.bytes_read
131 }
132
133 pub fn rows_returned(&self) -> u64 {
135 self.rows_returned
136 }
137}
138
139#[derive(Debug)]
141pub(crate) struct ScanBudget {
142 remaining_rows: u64,
143 remaining_bytes: u64,
144 metrics: ScanMetrics,
145}
146
147impl ScanBudget {
148 pub(crate) fn new(rows: u64, bytes: u64) -> Self {
149 Self {
150 remaining_rows: rows,
151 remaining_bytes: bytes,
152 metrics: ScanMetrics::default(),
153 }
154 }
155
156 pub(crate) fn charge_rows(&mut self, rows: u64) -> Result<()> {
157 self.remaining_rows = self
158 .remaining_rows
159 .checked_sub(rows)
160 .ok_or_else(scan_rows_exceeded)?;
161 Ok(())
162 }
163
164 pub(crate) fn record_block(&mut self, pruned: bool) -> Result<()> {
165 self.metrics.blocks_considered = self
166 .metrics
167 .blocks_considered
168 .checked_add(1)
169 .ok_or_else(metrics_overflow)?;
170 if pruned {
171 self.metrics.blocks_pruned = self
172 .metrics
173 .blocks_pruned
174 .checked_add(1)
175 .ok_or_else(metrics_overflow)?;
176 }
177 Ok(())
178 }
179
180 pub(crate) fn record_stream(&mut self, bytes: u64) -> Result<()> {
181 self.metrics.streams_decoded = self
182 .metrics
183 .streams_decoded
184 .checked_add(1)
185 .ok_or_else(metrics_overflow)?;
186 self.metrics.stream_bytes_decoded = self
187 .metrics
188 .stream_bytes_decoded
189 .checked_add(bytes)
190 .ok_or_else(metrics_overflow)?;
191 self.record_bytes_read(bytes)
192 }
193
194 pub(crate) fn record_bytes_read(&mut self, bytes: u64) -> Result<()> {
195 self.metrics.bytes_read = self
196 .metrics
197 .bytes_read
198 .checked_add(bytes)
199 .ok_or_else(metrics_overflow)?;
200 Ok(())
201 }
202
203 pub(crate) fn record_rows(&mut self, rows: usize) -> Result<()> {
204 let rows = u64::try_from(rows).map_err(|_| metrics_overflow())?;
205 self.metrics.rows_returned = self
206 .metrics
207 .rows_returned
208 .checked_add(rows)
209 .ok_or_else(metrics_overflow)?;
210 Ok(())
211 }
212
213 pub(crate) fn metrics(&self) -> ScanMetrics {
214 self.metrics
215 }
216}
217
218fn exceeded() -> Error {
219 Error::resource_limit(
220 "decoding this block exceeds the configured decoded-byte limit",
221 None,
222 )
223 .with_context(ErrorContext::Payload)
224}
225
226fn scan_exceeded() -> Error {
227 Error::resource_limit(
228 "the scan exceeds the configured cumulative decoded-byte limit",
229 None,
230 )
231 .with_context(ErrorContext::Payload)
232}
233
234fn scan_rows_exceeded() -> Error {
235 Error::resource_limit("the scan exceeds the configured cumulative row limit", None)
236 .with_context(ErrorContext::Payload)
237}
238
239fn metrics_overflow() -> Error {
240 Error::resource_limit("scan metrics arithmetic overflow", None)
241}
242
243#[cfg(test)]
244mod tests {
245 use super::Budget;
246 use crate::ErrorKind;
247
248 #[test]
249 fn charges_accumulate_across_calls() {
250 let mut budget = Budget::new(16);
251 budget.charge(8).expect("the first charge fits");
252
253 assert_eq!(
254 budget
255 .charge(9)
256 .expect_err("the second charge exceeds the allowance")
257 .kind(),
258 ErrorKind::ResourceLimit
259 );
260 }
261
262 #[test]
263 fn an_element_count_that_overflows_is_a_resource_limit() {
264 let mut budget = Budget::new(u64::MAX);
265
266 assert_eq!(
267 budget
268 .charge_elements(usize::MAX, 16)
269 .expect_err("the element byte count overflows")
270 .kind(),
271 ErrorKind::ResourceLimit
272 );
273 }
274}