icydb_core/db/query/plan/validate/
mod.rs1mod order;
11mod semantics;
12
13#[cfg(test)]
14mod tests;
15
16use crate::{
17 db::{
18 access::validate_access_plan_model as validate_access_plan_model_shared,
19 cursor::CursorPlanError,
20 policy::PlanPolicyError,
21 query::{
22 plan::{AccessPlannedQuery, OrderSpec},
23 predicate::{self, SchemaInfo},
24 },
25 },
26 model::entity::EntityModel,
27 value::Value,
28};
29use thiserror::Error as ThisError;
30
31pub(crate) use crate::db::access::AccessPlanError;
33#[cfg(test)]
34pub(crate) use crate::db::access::assess_secondary_order_pushdown_if_applicable;
35#[cfg(test)]
36pub(crate) use crate::db::access::{
37 PushdownApplicability, assess_secondary_order_pushdown_if_applicable_validated,
38};
39pub(crate) use crate::db::access::{
40 PushdownSurfaceEligibility, SecondaryOrderPushdownEligibility, SecondaryOrderPushdownRejection,
41 assess_secondary_order_pushdown,
42};
43pub(crate) use order::{
44 validate_no_duplicate_non_pk_order_fields, validate_order, validate_primary_key_tie_break,
45};
46
47#[derive(Debug, ThisError)]
57pub enum PlanError {
58 #[error("predicate validation failed: {0}")]
59 PredicateInvalid(Box<predicate::ValidateError>),
60
61 #[error("{0}")]
62 Order(Box<OrderPlanError>),
63
64 #[error("{0}")]
65 Access(Box<AccessPlanError>),
66
67 #[error("{0}")]
68 Policy(Box<PolicyPlanError>),
69
70 #[error("{0}")]
71 Cursor(Box<CursorPlanError>),
72}
73
74#[derive(Debug, ThisError)]
80pub enum OrderPlanError {
81 #[error("unknown order field '{field}'")]
83 UnknownField { field: String },
84
85 #[error("order field '{field}' is not orderable")]
87 UnorderableField { field: String },
88
89 #[error("order field '{field}' appears multiple times")]
91 DuplicateOrderField { field: String },
92
93 #[error("order specification must end with primary key '{field}' as deterministic tie-break")]
95 MissingPrimaryKeyTieBreak { field: String },
96}
97
98#[derive(Clone, Debug, Eq, PartialEq, ThisError)]
104pub enum PolicyPlanError {
105 #[error("order specification must include at least one field")]
107 EmptyOrderSpec,
108
109 #[error("delete plans must not include pagination")]
111 DeletePlanWithPagination,
112
113 #[error("load plans must not include delete limits")]
115 LoadPlanWithDeleteLimit,
116
117 #[error("delete limit requires an explicit ordering")]
119 DeleteLimitRequiresOrder,
120
121 #[error(
123 "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."
124 )]
125 UnorderedPagination,
126}
127
128impl From<PlanPolicyError> for PolicyPlanError {
129 fn from(err: PlanPolicyError) -> Self {
130 match err {
131 PlanPolicyError::EmptyOrderSpec => Self::EmptyOrderSpec,
132 PlanPolicyError::DeletePlanWithPagination => Self::DeletePlanWithPagination,
133 PlanPolicyError::LoadPlanWithDeleteLimit => Self::LoadPlanWithDeleteLimit,
134 PlanPolicyError::DeleteLimitRequiresOrder => Self::DeleteLimitRequiresOrder,
135 PlanPolicyError::UnorderedPagination => Self::UnorderedPagination,
136 }
137 }
138}
139
140impl From<predicate::ValidateError> for PlanError {
141 fn from(err: predicate::ValidateError) -> Self {
142 Self::PredicateInvalid(Box::new(err))
143 }
144}
145
146impl From<OrderPlanError> for PlanError {
147 fn from(err: OrderPlanError) -> Self {
148 Self::Order(Box::new(err))
149 }
150}
151
152impl From<AccessPlanError> for PlanError {
153 fn from(err: AccessPlanError) -> Self {
154 Self::Access(Box::new(err))
155 }
156}
157
158impl From<PolicyPlanError> for PlanError {
159 fn from(err: PolicyPlanError) -> Self {
160 Self::Policy(Box::new(err))
161 }
162}
163
164impl From<CursorPlanError> for PlanError {
165 fn from(err: CursorPlanError) -> Self {
166 Self::Cursor(Box::new(err))
167 }
168}
169
170impl From<PlanPolicyError> for PlanError {
171 fn from(err: PlanPolicyError) -> Self {
172 Self::from(PolicyPlanError::from(err))
173 }
174}
175
176pub(crate) fn validate_logical_plan_model(
185 schema: &SchemaInfo,
186 model: &EntityModel,
187 plan: &AccessPlannedQuery<Value>,
188) -> Result<(), PlanError> {
189 validate_plan_core(
190 schema,
191 model,
192 plan,
193 validate_order,
194 |schema, model, plan| {
195 validate_access_plan_model_shared(schema, model, &plan.access).map_err(PlanError::from)
196 },
197 )?;
198
199 Ok(())
200}
201
202fn validate_plan_core<K, FOrder, FAccess>(
204 schema: &SchemaInfo,
205 model: &EntityModel,
206 plan: &AccessPlannedQuery<K>,
207 validate_order_fn: FOrder,
208 validate_access_fn: FAccess,
209) -> Result<(), PlanError>
210where
211 FOrder: Fn(&SchemaInfo, &OrderSpec) -> Result<(), PlanError>,
212 FAccess: Fn(&SchemaInfo, &EntityModel, &AccessPlannedQuery<K>) -> Result<(), PlanError>,
213{
214 if let Some(predicate) = &plan.predicate {
215 predicate::validate(schema, predicate)?;
216 }
217
218 if let Some(order) = &plan.order {
219 validate_order_fn(schema, order)?;
220 validate_no_duplicate_non_pk_order_fields(model, order)?;
221 validate_primary_key_tie_break(model, order)?;
222 }
223
224 validate_access_fn(schema, model, plan)?;
225 semantics::validate_plan_semantics(plan)?;
226
227 Ok(())
228}