Skip to main content

hawdb_executor/
limit.rs

1// Copyright 2026 Nowledge
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use hawdb_core::{HawDBError, Result};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub struct ExecutionLimit {
19    pub output_rows: Option<usize>,
20}
21
22impl ExecutionLimit {
23    pub const fn unlimited() -> Self {
24        Self { output_rows: None }
25    }
26
27    pub fn from_user_max_rows(max_rows: Option<usize>) -> Result<Self> {
28        let Some(max_rows) = max_rows else {
29            return Ok(Self::unlimited());
30        };
31        let output_rows = max_rows.checked_add(1).ok_or_else(|| {
32            HawDBError::Execution("read query row limit is too large".to_string())
33        })?;
34        Ok(Self {
35            output_rows: Some(output_rows),
36        })
37    }
38
39    pub fn child_for_limit(self, offset: usize, limit: Option<usize>) -> Self {
40        let output_rows = match (self.output_rows, limit) {
41            (Some(cap), Some(limit)) => Some(offset.saturating_add(cap.min(limit))),
42            (Some(cap), None) => Some(offset.saturating_add(cap)),
43            (None, Some(limit)) => Some(offset.saturating_add(limit)),
44            (None, None) => None,
45        };
46        Self { output_rows }
47    }
48
49    pub fn is_reached(self, len: usize) -> bool {
50        self.output_rows.is_some_and(|cap| len >= cap)
51    }
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57
58    #[test]
59    fn user_limit_keeps_one_extra_detection_row() {
60        let limit = ExecutionLimit::from_user_max_rows(Some(10)).unwrap();
61        assert_eq!(limit.output_rows, Some(11));
62        assert!(limit.is_reached(11));
63        assert!(!limit.is_reached(10));
64    }
65
66    #[test]
67    fn nested_limit_keeps_parent_detection_cap() {
68        let limit = ExecutionLimit {
69            output_rows: Some(11),
70        };
71        assert_eq!(
72            limit.child_for_limit(5, Some(3)),
73            ExecutionLimit {
74                output_rows: Some(8)
75            }
76        );
77    }
78}