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::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        // A usize::MAX ceiling cannot be exceeded by a representable result.
32        // There is no additional detection row to admit at that boundary.
33        Ok(Self {
34            output_rows: max_rows.checked_add(1),
35        })
36    }
37
38    pub fn child_for_limit(self, offset: usize, limit: Option<usize>) -> Self {
39        let output_rows = match (self.output_rows, limit) {
40            (Some(cap), Some(limit)) => Some(offset.saturating_add(cap.min(limit))),
41            (Some(cap), None) => Some(offset.saturating_add(cap)),
42            (None, Some(limit)) => Some(offset.saturating_add(limit)),
43            (None, None) => None,
44        };
45        Self { output_rows }
46    }
47
48    pub fn is_reached(self, len: usize) -> bool {
49        self.output_rows.is_some_and(|cap| len >= cap)
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    #[test]
58    fn user_limit_keeps_one_extra_detection_row() {
59        let limit = ExecutionLimit::from_user_max_rows(Some(10)).unwrap();
60        assert_eq!(limit.output_rows, Some(11));
61        assert!(limit.is_reached(11));
62        assert!(!limit.is_reached(10));
63    }
64
65    #[test]
66    fn nested_limit_keeps_parent_detection_cap() {
67        let limit = ExecutionLimit {
68            output_rows: Some(11),
69        };
70        assert_eq!(
71            limit.child_for_limit(5, Some(3)),
72            ExecutionLimit {
73                output_rows: Some(8)
74            }
75        );
76    }
77}