icydb_core/db/query/plan/validate/
mod.rs1mod core;
11mod grouped;
12mod order;
13mod policy;
14
15use crate::db::{access::AccessPlanError, cursor::CursorPlanError, predicate::ValidateError};
16use thiserror::Error as ThisError;
17
18pub(crate) use core::{validate_group_query_semantics, validate_query_semantics};
19#[cfg(test)]
20pub(in crate::db::query) use grouped::validate_group_projection_expr_compatibility_for_test;
21pub(crate) use order::validate_order;
22pub(crate) use policy::{
23 has_explicit_order, resolve_group_field_slot, validate_cursor_order_plan_shape,
24 validate_cursor_paging_requirements, validate_fluent_non_paged_mode,
25 validate_fluent_paged_mode, validate_intent_key_access_policy, validate_intent_plan_shape,
26 validate_order_shape,
27};
28
29#[derive(Debug, ThisError)]
39pub enum PlanError {
40 #[error("predicate validation failed: {0}")]
41 PredicateInvalid(Box<ValidateError>),
42
43 #[error("{0}")]
44 Order(Box<OrderPlanError>),
45
46 #[error("{0}")]
47 Access(Box<AccessPlanError>),
48
49 #[error("{0}")]
50 Policy(Box<PolicyPlanError>),
51
52 #[error("{0}")]
53 Cursor(Box<CursorPlanError>),
54
55 #[error("{0}")]
56 Group(Box<GroupPlanError>),
57
58 #[error("{0}")]
59 Expr(Box<ExprPlanError>),
60}
61
62#[derive(Debug, ThisError)]
69pub enum OrderPlanError {
70 #[error("unknown order field '{field}'")]
72 UnknownField { field: String },
73
74 #[error("order field '{field}' is not orderable")]
76 UnorderableField { field: String },
77
78 #[error("order field '{field}' appears multiple times")]
80 DuplicateOrderField { field: String },
81
82 #[error("order specification must end with primary key '{field}' as deterministic tie-break")]
84 MissingPrimaryKeyTieBreak { field: String },
85}
86
87#[derive(Clone, Copy, Debug, Eq, PartialEq, ThisError)]
94pub enum PolicyPlanError {
95 #[error("order specification must include at least one field")]
97 EmptyOrderSpec,
98
99 #[error("delete plans must not include pagination")]
101 DeletePlanWithPagination,
102
103 #[error("load plans must not include delete limits")]
105 LoadPlanWithDeleteLimit,
106
107 #[error("delete limit requires an explicit ordering")]
109 DeleteLimitRequiresOrder,
110
111 #[error(
113 "Unordered pagination is not allowed.\nThis query uses LIMIT or OFFSET without an ORDER BY clause.\nPagination without a total ordering is non-deterministic.\nAdd an explicit order_by(...) to make the query stable."
114 )]
115 UnorderedPagination,
116}
117
118#[derive(Clone, Copy, Debug, Eq, PartialEq, ThisError)]
125pub enum CursorPagingPolicyError {
126 #[error(
127 "{message}",
128 message = CursorPlanError::cursor_requires_order_message()
129 )]
130 CursorRequiresOrder,
131
132 #[error(
133 "{message}",
134 message = CursorPlanError::cursor_requires_limit_message()
135 )]
136 CursorRequiresLimit,
137}
138
139#[derive(Clone, Debug, Eq, PartialEq, ThisError)]
146pub enum GroupPlanError {
147 #[error("HAVING is only supported for GROUP BY queries in this release")]
149 HavingRequiresGroupBy,
150
151 #[error("group query validation requires grouped logical plan variant")]
153 GroupedLogicalPlanRequired,
154
155 #[error("group specification must include at least one group field")]
157 EmptyGroupFields,
158
159 #[error(
161 "global DISTINCT aggregate without GROUP BY must declare exactly one DISTINCT field-target aggregate in this release"
162 )]
163 GlobalDistinctAggregateShapeUnsupported,
164
165 #[error("group specification must include at least one aggregate terminal")]
167 EmptyAggregates,
168
169 #[error("unknown group field '{field}'")]
171 UnknownGroupField { field: String },
172
173 #[error("group specification has duplicate group key: '{field}'")]
175 DuplicateGroupField { field: String },
176
177 #[error(
179 "grouped DISTINCT requires adjacency-based ordered-group eligibility proof in this release"
180 )]
181 DistinctAdjacencyEligibilityRequired,
182
183 #[error("grouped ORDER BY must start with GROUP BY key prefix in this release")]
185 OrderPrefixNotAlignedWithGroupKeys,
186
187 #[error("grouped ORDER BY requires LIMIT in this release")]
189 OrderRequiresLimit,
190
191 #[error("grouped HAVING with DISTINCT is not supported in this release")]
193 DistinctHavingUnsupported,
194
195 #[error("grouped HAVING clause at index={index} uses unsupported operator: {op}")]
197 HavingUnsupportedCompareOp { index: usize, op: String },
198
199 #[error("grouped HAVING clause at index={index} references non-group field '{field}'")]
201 HavingNonGroupFieldReference { index: usize, field: String },
202
203 #[error(
205 "grouped HAVING clause at index={index} references aggregate index {aggregate_index} but aggregate_count={aggregate_count}"
206 )]
207 HavingAggregateIndexOutOfBounds {
208 index: usize,
209 aggregate_index: usize,
210 aggregate_count: usize,
211 },
212
213 #[error(
215 "grouped DISTINCT aggregate at index={index} uses unsupported kind '{kind}' in this release"
216 )]
217 DistinctAggregateKindUnsupported { index: usize, kind: String },
218
219 #[error(
221 "grouped DISTINCT aggregate at index={index} cannot target field '{field}' in this release: found {kind}"
222 )]
223 DistinctAggregateFieldTargetUnsupported {
224 index: usize,
225 kind: String,
226 field: String,
227 },
228
229 #[error("unknown grouped aggregate target field at index={index}: '{field}'")]
231 UnknownAggregateTargetField { index: usize, field: String },
232
233 #[error(
235 "global DISTINCT SUM aggregate target field at index={index} is not numeric: '{field}'"
236 )]
237 GlobalDistinctSumTargetNotNumeric { index: usize, field: String },
238
239 #[error(
241 "grouped aggregate at index={index} cannot target field '{field}' in this release: found {kind}"
242 )]
243 FieldTargetAggregatesUnsupported {
244 index: usize,
245 kind: String,
246 field: String,
247 },
248}
249
250#[derive(Clone, Debug, Eq, PartialEq, ThisError)]
257pub enum ExprPlanError {
258 #[error("unknown expression field '{field}'")]
260 UnknownExprField { field: String },
261
262 #[error("aggregate '{kind}' requires numeric target field '{field}'")]
264 NonNumericAggregateTarget { kind: String, field: String },
265
266 #[error("aggregate '{kind}' requires an explicit target field")]
268 AggregateTargetRequired { kind: String },
269
270 #[error("unary operator '{op}' is incompatible with operand type {found}")]
272 InvalidUnaryOperand { op: String, found: String },
273
274 #[error("binary operator '{op}' is incompatible with operand types ({left}, {right})")]
276 InvalidBinaryOperands {
277 op: String,
278 left: String,
279 right: String,
280 },
281
282 #[error(
284 "grouped projection expression at index={index} references fields outside GROUP BY keys"
285 )]
286 GroupedProjectionReferencesNonGroupField { index: usize },
287}
288
289#[derive(Clone, Copy, Debug, Eq, PartialEq)]
296pub(crate) enum CursorOrderPlanShapeError {
297 MissingExplicitOrder,
298 EmptyOrderSpec,
299}
300
301#[derive(Clone, Copy, Debug, Eq, PartialEq)]
308pub(crate) enum IntentKeyAccessKind {
309 Single,
310 Many,
311 Only,
312}
313
314#[derive(Clone, Copy, Debug, Eq, PartialEq)]
320pub(crate) enum IntentKeyAccessPolicyViolation {
321 KeyAccessConflict,
322 ByIdsWithPredicate,
323 OnlyWithPredicate,
324}
325
326#[derive(Clone, Copy, Debug, Eq, PartialEq)]
333pub(crate) enum FluentLoadPolicyViolation {
334 CursorRequiresPagedExecution,
335 GroupedRequiresExecuteGrouped,
336 CursorRequiresOrder,
337 CursorRequiresLimit,
338}
339
340impl From<ValidateError> for PlanError {
341 fn from(err: ValidateError) -> Self {
342 Self::PredicateInvalid(Box::new(err))
343 }
344}
345
346impl From<OrderPlanError> for PlanError {
347 fn from(err: OrderPlanError) -> Self {
348 Self::Order(Box::new(err))
349 }
350}
351
352impl From<AccessPlanError> for PlanError {
353 fn from(err: AccessPlanError) -> Self {
354 Self::Access(Box::new(err))
355 }
356}
357
358impl From<PolicyPlanError> for PlanError {
359 fn from(err: PolicyPlanError) -> Self {
360 Self::Policy(Box::new(err))
361 }
362}
363
364impl From<CursorPlanError> for PlanError {
365 fn from(err: CursorPlanError) -> Self {
366 Self::Cursor(Box::new(err))
367 }
368}
369
370impl From<GroupPlanError> for PlanError {
371 fn from(err: GroupPlanError) -> Self {
372 Self::Group(Box::new(err))
373 }
374}
375
376impl From<ExprPlanError> for PlanError {
377 fn from(err: ExprPlanError) -> Self {
378 Self::Expr(Box::new(err))
379 }
380}