icydb_core/db/query/plan/validate/
mod.rs1mod access;
12mod order;
13mod pushdown;
14mod semantics;
15
16#[cfg(test)]
17mod tests;
18
19use crate::{
20 db::{
21 cursor::CursorPlanError,
22 plan::{AccessPlannedQuery, OrderSpec},
23 query::{
24 policy::PlanPolicyError,
25 predicate::{self, SchemaInfo},
26 },
27 },
28 error::InternalError,
29 model::{entity::EntityModel, index::IndexModel},
30 traits::EntityKind,
31 value::Value,
32};
33use thiserror::Error as ThisError;
34
35pub(crate) use access::{validate_access_plan, validate_access_plan_model};
37pub(crate) use order::{
38 validate_no_duplicate_non_pk_order_fields, validate_order, validate_primary_key_tie_break,
39};
40#[cfg(test)]
41pub(crate) use pushdown::assess_secondary_order_pushdown_if_applicable;
42#[cfg(test)]
43pub(crate) use pushdown::{
44 PushdownApplicability, assess_secondary_order_pushdown_if_applicable_validated,
45};
46pub(crate) use pushdown::{
47 PushdownSurfaceEligibility, SecondaryOrderPushdownEligibility, SecondaryOrderPushdownRejection,
48 assess_secondary_order_pushdown,
49};
50
51#[derive(Debug, ThisError)]
61pub enum PlanError {
62 #[error("predicate validation failed: {0}")]
63 PredicateInvalid(Box<predicate::ValidateError>),
64
65 #[error("{0}")]
66 Order(Box<OrderPlanError>),
67
68 #[error("{0}")]
69 Access(Box<AccessPlanError>),
70
71 #[error("{0}")]
72 Policy(Box<PolicyPlanError>),
73
74 #[error("{0}")]
75 Cursor(Box<CursorPlanError>),
76}
77
78#[derive(Debug, ThisError)]
84pub enum OrderPlanError {
85 #[error("unknown order field '{field}'")]
87 UnknownField { field: String },
88
89 #[error("order field '{field}' is not orderable")]
91 UnorderableField { field: String },
92
93 #[error("order field '{field}' appears multiple times")]
95 DuplicateOrderField { field: String },
96
97 #[error("order specification must end with primary key '{field}' as deterministic tie-break")]
99 MissingPrimaryKeyTieBreak { field: String },
100}
101
102#[derive(Debug, ThisError)]
108pub enum AccessPlanError {
109 #[error("index '{index}' not found on entity")]
111 IndexNotFound { index: IndexModel },
112
113 #[error("index prefix length {prefix_len} exceeds index field count {field_len}")]
115 IndexPrefixTooLong { prefix_len: usize, field_len: usize },
116
117 #[error("index prefix must include at least one value")]
119 IndexPrefixEmpty,
120
121 #[error("index prefix value for field '{field}' is incompatible")]
123 IndexPrefixValueMismatch { field: String },
124
125 #[error("primary key field '{field}' is not key-compatible")]
127 PrimaryKeyNotKeyable { field: String },
128
129 #[error("key '{key:?}' is incompatible with primary key '{field}'")]
131 PrimaryKeyMismatch { field: String, key: Value },
132
133 #[error("key range start is greater than end")]
135 InvalidKeyRange,
136}
137
138#[derive(Clone, Debug, Eq, PartialEq, ThisError)]
144pub enum PolicyPlanError {
145 #[error("order specification must include at least one field")]
147 EmptyOrderSpec,
148
149 #[error("delete plans must not include pagination")]
151 DeletePlanWithPagination,
152
153 #[error("load plans must not include delete limits")]
155 LoadPlanWithDeleteLimit,
156
157 #[error("delete limit requires an explicit ordering")]
159 DeleteLimitRequiresOrder,
160
161 #[error(
163 "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."
164 )]
165 UnorderedPagination,
166}
167
168impl From<PlanPolicyError> for PolicyPlanError {
169 fn from(err: PlanPolicyError) -> Self {
170 match err {
171 PlanPolicyError::EmptyOrderSpec => Self::EmptyOrderSpec,
172 PlanPolicyError::DeletePlanWithPagination => Self::DeletePlanWithPagination,
173 PlanPolicyError::LoadPlanWithDeleteLimit => Self::LoadPlanWithDeleteLimit,
174 PlanPolicyError::DeleteLimitRequiresOrder => Self::DeleteLimitRequiresOrder,
175 PlanPolicyError::UnorderedPagination => Self::UnorderedPagination,
176 }
177 }
178}
179
180impl From<predicate::ValidateError> for PlanError {
181 fn from(err: predicate::ValidateError) -> Self {
182 Self::PredicateInvalid(Box::new(err))
183 }
184}
185
186impl From<OrderPlanError> for PlanError {
187 fn from(err: OrderPlanError) -> Self {
188 Self::Order(Box::new(err))
189 }
190}
191
192impl From<AccessPlanError> for PlanError {
193 fn from(err: AccessPlanError) -> Self {
194 Self::Access(Box::new(err))
195 }
196}
197
198impl From<PolicyPlanError> for PlanError {
199 fn from(err: PolicyPlanError) -> Self {
200 Self::Policy(Box::new(err))
201 }
202}
203
204impl From<CursorPlanError> for PlanError {
205 fn from(err: CursorPlanError) -> Self {
206 Self::Cursor(Box::new(err))
207 }
208}
209
210impl From<PlanPolicyError> for PlanError {
211 fn from(err: PlanPolicyError) -> Self {
212 Self::from(PolicyPlanError::from(err))
213 }
214}
215
216pub(crate) fn validate_logical_plan_model(
225 schema: &SchemaInfo,
226 model: &EntityModel,
227 plan: &AccessPlannedQuery<Value>,
228) -> Result<(), PlanError> {
229 validate_plan_core(
230 schema,
231 model,
232 plan,
233 validate_order,
234 |schema, model, plan| validate_access_plan_model(schema, model, &plan.access),
235 )?;
236
237 Ok(())
238}
239
240pub(crate) fn validate_executor_plan<E: EntityKind>(
249 plan: &AccessPlannedQuery<E::Key>,
250) -> Result<(), InternalError> {
251 let schema = SchemaInfo::from_entity_model(E::MODEL).map_err(|err| {
252 InternalError::query_invariant(format!("entity schema invalid for {}: {err}", E::PATH))
253 })?;
254
255 validate_access_plan(&schema, E::MODEL, &plan.access)
256 .map_err(InternalError::from_executor_plan_error)?;
257
258 Ok(())
259}
260
261fn validate_plan_core<K, FOrder, FAccess>(
263 schema: &SchemaInfo,
264 model: &EntityModel,
265 plan: &AccessPlannedQuery<K>,
266 validate_order_fn: FOrder,
267 validate_access_fn: FAccess,
268) -> Result<(), PlanError>
269where
270 FOrder: Fn(&SchemaInfo, &OrderSpec) -> Result<(), PlanError>,
271 FAccess: Fn(&SchemaInfo, &EntityModel, &AccessPlannedQuery<K>) -> Result<(), PlanError>,
272{
273 if let Some(predicate) = &plan.predicate {
274 predicate::validate(schema, predicate)?;
275 }
276
277 if let Some(order) = &plan.order {
278 validate_order_fn(schema, order)?;
279 validate_no_duplicate_non_pk_order_fields(model, order)?;
280 validate_primary_key_tie_break(model, order)?;
281 }
282
283 validate_access_fn(schema, model, plan)?;
284 semantics::validate_plan_semantics(plan)?;
285
286 Ok(())
287}