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)]
38pub enum PlanError {
39 #[error("{0}")]
40 Semantic(Box<SemanticPlanError>),
41
42 #[error("{0}")]
43 Cursor(Box<CursorPlanError>),
44}
45
46#[derive(Debug, ThisError)]
54pub enum SemanticPlanError {
55 #[error("predicate validation failed: {0}")]
56 PredicateInvalid(Box<ValidateError>),
57
58 #[error("{0}")]
59 Order(Box<OrderPlanError>),
60
61 #[error("{0}")]
62 Access(Box<AccessPlanError>),
63
64 #[error("{0}")]
65 Policy(Box<PolicyPlanError>),
66
67 #[error("{0}")]
68 Group(Box<GroupPlanError>),
69
70 #[error("{0}")]
71 Expr(Box<ExprPlanError>),
72}
73
74#[derive(Debug, ThisError)]
81pub enum OrderPlanError {
82 #[error("unknown order field '{field}'")]
84 UnknownField { field: String },
85
86 #[error("order field '{field}' is not orderable")]
88 UnorderableField { field: String },
89
90 #[error("order field '{field}' appears multiple times")]
92 DuplicateOrderField { field: String },
93
94 #[error("order specification must end with primary key '{field}' as deterministic tie-break")]
96 MissingPrimaryKeyTieBreak { field: String },
97}
98
99#[derive(Clone, Copy, Debug, Eq, PartialEq, ThisError)]
106pub enum PolicyPlanError {
107 #[error("order specification must include at least one field")]
109 EmptyOrderSpec,
110
111 #[error("delete plans must not include pagination")]
113 DeletePlanWithPagination,
114
115 #[error("load plans must not include delete limits")]
117 LoadPlanWithDeleteLimit,
118
119 #[error("delete limit requires an explicit ordering")]
121 DeleteLimitRequiresOrder,
122
123 #[error(
125 "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."
126 )]
127 UnorderedPagination,
128}
129
130#[derive(Clone, Copy, Debug, Eq, PartialEq, ThisError)]
137pub enum CursorPagingPolicyError {
138 #[error(
139 "{message}",
140 message = CursorPlanError::cursor_requires_order_message()
141 )]
142 CursorRequiresOrder,
143
144 #[error(
145 "{message}",
146 message = CursorPlanError::cursor_requires_limit_message()
147 )]
148 CursorRequiresLimit,
149}
150
151#[derive(Clone, Debug, Eq, PartialEq, ThisError)]
158pub enum GroupPlanError {
159 #[error("HAVING is only supported for GROUP BY queries in this release")]
161 HavingRequiresGroupBy,
162
163 #[error("group query validation requires grouped logical plan variant")]
165 GroupedLogicalPlanRequired,
166
167 #[error("group specification must include at least one group field")]
169 EmptyGroupFields,
170
171 #[error(
173 "global DISTINCT aggregate without GROUP BY must declare exactly one DISTINCT field-target aggregate in this release"
174 )]
175 GlobalDistinctAggregateShapeUnsupported,
176
177 #[error("group specification must include at least one aggregate terminal")]
179 EmptyAggregates,
180
181 #[error("unknown group field '{field}'")]
183 UnknownGroupField { field: String },
184
185 #[error("group specification has duplicate group key: '{field}'")]
187 DuplicateGroupField { field: String },
188
189 #[error(
191 "grouped DISTINCT requires adjacency-based ordered-group eligibility proof in this release"
192 )]
193 DistinctAdjacencyEligibilityRequired,
194
195 #[error("grouped ORDER BY must start with GROUP BY key prefix in this release")]
197 OrderPrefixNotAlignedWithGroupKeys,
198
199 #[error("grouped ORDER BY requires LIMIT in this release")]
201 OrderRequiresLimit,
202
203 #[error("grouped HAVING with DISTINCT is not supported in this release")]
205 DistinctHavingUnsupported,
206
207 #[error("grouped HAVING clause at index={index} uses unsupported operator: {op}")]
209 HavingUnsupportedCompareOp { index: usize, op: String },
210
211 #[error("grouped HAVING clause at index={index} references non-group field '{field}'")]
213 HavingNonGroupFieldReference { index: usize, field: String },
214
215 #[error(
217 "grouped HAVING clause at index={index} references aggregate index {aggregate_index} but aggregate_count={aggregate_count}"
218 )]
219 HavingAggregateIndexOutOfBounds {
220 index: usize,
221 aggregate_index: usize,
222 aggregate_count: usize,
223 },
224
225 #[error(
227 "grouped DISTINCT aggregate at index={index} uses unsupported kind '{kind}' in this release"
228 )]
229 DistinctAggregateKindUnsupported { index: usize, kind: String },
230
231 #[error(
233 "grouped DISTINCT aggregate at index={index} cannot target field '{field}' in this release: found {kind}"
234 )]
235 DistinctAggregateFieldTargetUnsupported {
236 index: usize,
237 kind: String,
238 field: String,
239 },
240
241 #[error("unknown grouped aggregate target field at index={index}: '{field}'")]
243 UnknownAggregateTargetField { index: usize, field: String },
244
245 #[error(
247 "global DISTINCT SUM aggregate target field at index={index} is not numeric: '{field}'"
248 )]
249 GlobalDistinctSumTargetNotNumeric { index: usize, field: String },
250
251 #[error(
253 "grouped aggregate at index={index} cannot target field '{field}' in this release: found {kind}"
254 )]
255 FieldTargetAggregatesUnsupported {
256 index: usize,
257 kind: String,
258 field: String,
259 },
260}
261
262#[derive(Clone, Debug, Eq, PartialEq, ThisError)]
269pub enum ExprPlanError {
270 #[error("unknown expression field '{field}'")]
272 UnknownExprField { field: String },
273
274 #[error("aggregate '{kind}' requires numeric target field '{field}'")]
276 NonNumericAggregateTarget { kind: String, field: String },
277
278 #[error("aggregate '{kind}' requires an explicit target field")]
280 AggregateTargetRequired { kind: String },
281
282 #[error("unary operator '{op}' is incompatible with operand type {found}")]
284 InvalidUnaryOperand { op: String, found: String },
285
286 #[error("binary operator '{op}' is incompatible with operand types ({left}, {right})")]
288 InvalidBinaryOperands {
289 op: String,
290 left: String,
291 right: String,
292 },
293
294 #[error(
296 "grouped projection expression at index={index} references fields outside GROUP BY keys"
297 )]
298 GroupedProjectionReferencesNonGroupField { index: usize },
299}
300
301#[derive(Clone, Copy, Debug, Eq, PartialEq)]
308pub(crate) enum CursorOrderPlanShapeError {
309 MissingExplicitOrder,
310 EmptyOrderSpec,
311}
312
313#[derive(Clone, Copy, Debug, Eq, PartialEq)]
320pub(crate) enum IntentKeyAccessKind {
321 Single,
322 Many,
323 Only,
324}
325
326#[derive(Clone, Copy, Debug, Eq, PartialEq)]
332pub(crate) enum IntentKeyAccessPolicyViolation {
333 KeyAccessConflict,
334 ByIdsWithPredicate,
335 OnlyWithPredicate,
336}
337
338#[derive(Clone, Copy, Debug, Eq, PartialEq)]
345pub(crate) enum FluentLoadPolicyViolation {
346 CursorRequiresPagedExecution,
347 GroupedRequiresExecuteGrouped,
348 CursorRequiresOrder,
349 CursorRequiresLimit,
350}
351
352impl From<ValidateError> for PlanError {
353 fn from(err: ValidateError) -> Self {
354 Self::from(SemanticPlanError::from(err))
355 }
356}
357
358impl From<OrderPlanError> for PlanError {
359 fn from(err: OrderPlanError) -> Self {
360 Self::from(SemanticPlanError::from(err))
361 }
362}
363
364impl From<AccessPlanError> for PlanError {
365 fn from(err: AccessPlanError) -> Self {
366 Self::from(SemanticPlanError::from(err))
367 }
368}
369
370impl From<PolicyPlanError> for PlanError {
371 fn from(err: PolicyPlanError) -> Self {
372 Self::from(SemanticPlanError::from(err))
373 }
374}
375
376impl From<CursorPlanError> for PlanError {
377 fn from(err: CursorPlanError) -> Self {
378 Self::Cursor(Box::new(err))
379 }
380}
381
382impl From<GroupPlanError> for PlanError {
383 fn from(err: GroupPlanError) -> Self {
384 Self::from(SemanticPlanError::from(err))
385 }
386}
387
388impl From<ExprPlanError> for PlanError {
389 fn from(err: ExprPlanError) -> Self {
390 Self::from(SemanticPlanError::from(err))
391 }
392}
393
394impl From<SemanticPlanError> for PlanError {
395 fn from(err: SemanticPlanError) -> Self {
396 Self::Semantic(Box::new(err))
397 }
398}
399
400impl From<ValidateError> for SemanticPlanError {
401 fn from(err: ValidateError) -> Self {
402 Self::PredicateInvalid(Box::new(err))
403 }
404}
405
406impl From<OrderPlanError> for SemanticPlanError {
407 fn from(err: OrderPlanError) -> Self {
408 Self::Order(Box::new(err))
409 }
410}
411
412impl From<AccessPlanError> for SemanticPlanError {
413 fn from(err: AccessPlanError) -> Self {
414 Self::Access(Box::new(err))
415 }
416}
417
418impl From<PolicyPlanError> for SemanticPlanError {
419 fn from(err: PolicyPlanError) -> Self {
420 Self::Policy(Box::new(err))
421 }
422}
423
424impl From<GroupPlanError> for SemanticPlanError {
425 fn from(err: GroupPlanError) -> Self {
426 Self::Group(Box::new(err))
427 }
428}
429
430impl From<ExprPlanError> for SemanticPlanError {
431 fn from(err: ExprPlanError) -> Self {
432 Self::Expr(Box::new(err))
433 }
434}