Skip to main content

riffdb_types/
query.rs

1//! Shared whole-query cost identities and bounds.
2
3/// Hard maximum access steps in one compiled application query.
4pub const MAX_APPLICATION_QUERY_STEPS: u64 = 64;
5/// Hard maximum encoded application-query result bytes.
6pub const MAX_APPLICATION_QUERY_RESULT_BYTES: u64 = 4_194_304;
7/// Hard maximum physical rows inspected by one application-query access step.
8///
9/// One row is reserved for continuation detection, so the largest result page
10/// is `MAX_APPLICATION_QUERY_PAGE_ROWS`.
11pub const MAX_APPLICATION_QUERY_SCANNED_ROWS: u64 = 65_535;
12/// Hard maximum rows returned by one application-query page.
13pub const MAX_APPLICATION_QUERY_PAGE_ROWS: u64 = MAX_APPLICATION_QUERY_SCANNED_ROWS - 1;
14/// Largest page encoded by the predecessor bounded-limit V1 identity.
15pub const MAX_APPLICATION_QUERY_PAGE_ROWS_BOUNDED_LIMIT_V1: u64 = 499;
16/// Existing exact-vector provider partition ceiling, independent from page rows.
17pub const MAX_EXACT_VECTOR_PARTITION_ROWS_V1: u64 = 500;
18
19/// Canonical compiler-derived maximum work for one complete query request.
20///
21/// This value is part of the query-plan hash input. It is deliberately a
22/// vector rather than a scalar so one resource cannot hide amplification in
23/// another.
24#[derive(Clone, Copy, Debug, Eq, PartialEq)]
25pub struct QueryCostVectorV1 {
26    access_steps: u64,
27    scanned_index_rows: u64,
28    point_reads: u64,
29    dependent_keys: u64,
30    intermediate_rows: u64,
31    projected_values: u64,
32    encoded_result_bytes: u64,
33}
34
35impl QueryCostVectorV1 {
36    /// Constructs one complete vector. Zero is valid for dimensions a plan
37    /// does not use; a compiled query must still have at least one step.
38    #[allow(clippy::too_many_arguments)]
39    #[must_use]
40    pub const fn new(
41        access_steps: u64,
42        scanned_index_rows: u64,
43        point_reads: u64,
44        dependent_keys: u64,
45        intermediate_rows: u64,
46        projected_values: u64,
47        encoded_result_bytes: u64,
48    ) -> Option<Self> {
49        if access_steps == 0
50            || access_steps > MAX_APPLICATION_QUERY_STEPS
51            || encoded_result_bytes > MAX_APPLICATION_QUERY_RESULT_BYTES
52        {
53            return None;
54        }
55        Some(Self {
56            access_steps,
57            scanned_index_rows,
58            point_reads,
59            dependent_keys,
60            intermediate_rows,
61            projected_values,
62            encoded_result_bytes,
63        })
64    }
65
66    /// Constructs a zero vector for execution accounting and tests.
67    #[must_use]
68    pub const fn zero() -> Self {
69        Self {
70            access_steps: 0,
71            scanned_index_rows: 0,
72            point_reads: 0,
73            dependent_keys: 0,
74            intermediate_rows: 0,
75            projected_values: 0,
76            encoded_result_bytes: 0,
77        }
78    }
79
80    /// Maximum access steps.
81    #[must_use]
82    pub const fn access_steps(self) -> u64 {
83        self.access_steps
84    }
85
86    /// Maximum physically inspected index rows.
87    #[must_use]
88    pub const fn scanned_index_rows(self) -> u64 {
89        self.scanned_index_rows
90    }
91
92    /// Maximum authoritative entity point reads, including index hydration.
93    #[must_use]
94    pub const fn point_reads(self) -> u64 {
95        self.point_reads
96    }
97
98    /// Maximum keys produced for dependent batches.
99    #[must_use]
100    pub const fn dependent_keys(self) -> u64 {
101        self.dependent_keys
102    }
103
104    /// Maximum rows retained between access steps.
105    #[must_use]
106    pub const fn intermediate_rows(self) -> u64 {
107        self.intermediate_rows
108    }
109
110    /// Maximum field values shaped into the declared result.
111    #[must_use]
112    pub const fn projected_values(self) -> u64 {
113        self.projected_values
114    }
115
116    /// Maximum encoded result bytes.
117    #[must_use]
118    pub const fn encoded_result_bytes(self) -> u64 {
119        self.encoded_result_bytes
120    }
121
122    /// Whether every requested dimension fits this complete budget.
123    #[must_use]
124    pub const fn covers(self, requested: Self) -> bool {
125        requested.access_steps <= self.access_steps
126            && requested.scanned_index_rows <= self.scanned_index_rows
127            && requested.point_reads <= self.point_reads
128            && requested.dependent_keys <= self.dependent_keys
129            && requested.intermediate_rows <= self.intermediate_rows
130            && requested.projected_values <= self.projected_values
131            && requested.encoded_result_bytes <= self.encoded_result_bytes
132    }
133}
134
135// Exhaustive non-durable ownership inventories for exact-provider admission.
136crate::inspect_owned_capacity_copy_v1!(QueryCostVectorV1);
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    #[test]
143    fn complete_vector_coverage_rejects_one_over_in_every_dimension() {
144        let budget = QueryCostVectorV1::new(7, 11, 13, 17, 19, 23, 29).expect("budget");
145        assert!(budget.covers(budget));
146        let candidates = [
147            QueryCostVectorV1::new(8, 11, 13, 17, 19, 23, 29),
148            QueryCostVectorV1::new(7, 12, 13, 17, 19, 23, 29),
149            QueryCostVectorV1::new(7, 11, 14, 17, 19, 23, 29),
150            QueryCostVectorV1::new(7, 11, 13, 18, 19, 23, 29),
151            QueryCostVectorV1::new(7, 11, 13, 17, 20, 23, 29),
152            QueryCostVectorV1::new(7, 11, 13, 17, 19, 24, 29),
153            QueryCostVectorV1::new(7, 11, 13, 17, 19, 23, 30),
154        ];
155        assert!(
156            candidates
157                .into_iter()
158                .all(|candidate| { candidate.is_none_or(|candidate| !budget.covers(candidate)) })
159        );
160    }
161
162    #[test]
163    fn empty_work_is_a_valid_accounting_value_but_not_a_compiled_plan() {
164        assert_eq!(QueryCostVectorV1::zero().access_steps(), 0);
165        assert!(QueryCostVectorV1::new(0, 0, 0, 0, 0, 0, 0).is_none());
166        assert!(
167            QueryCostVectorV1::new(1, 0, 0, 0, 0, 0, MAX_APPLICATION_QUERY_RESULT_BYTES).is_some()
168        );
169        assert!(
170            QueryCostVectorV1::new(1, 0, 0, 0, 0, 0, MAX_APPLICATION_QUERY_RESULT_BYTES + 1,)
171                .is_none()
172        );
173    }
174
175    #[test]
176    fn page_rows_reserve_exactly_one_continuation_probe() {
177        assert_eq!(MAX_APPLICATION_QUERY_SCANNED_ROWS, 65_535);
178        assert_eq!(MAX_APPLICATION_QUERY_PAGE_ROWS, 65_534);
179        assert_eq!(MAX_APPLICATION_QUERY_PAGE_ROWS_BOUNDED_LIMIT_V1, 499);
180        assert_eq!(MAX_EXACT_VECTOR_PARTITION_ROWS_V1, 500);
181    }
182}