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::query::{
21 plan::{AccessPlannedQuery, cursor::CursorPlanError},
22 policy::PlanPolicyError,
23 predicate::{self, SchemaInfo},
24 },
25 error::InternalError,
26 model::{entity::EntityModel, index::IndexModel},
27 traits::EntityKind,
28 value::Value,
29};
30use thiserror::Error as ThisError;
31
32pub(crate) use access::{validate_access_plan, validate_access_plan_model};
34pub(crate) use order::{
35 validate_no_duplicate_non_pk_order_fields, validate_order, validate_primary_key_tie_break,
36};
37#[cfg(test)]
38pub(crate) use pushdown::assess_secondary_order_pushdown_if_applicable;
39pub(crate) use pushdown::{
40 PushdownApplicability, PushdownSurfaceEligibility, SecondaryOrderPushdownEligibility,
41 SecondaryOrderPushdownRejection, assess_secondary_order_pushdown,
42 assess_secondary_order_pushdown_if_applicable_validated,
43};
44
45#[derive(Debug, ThisError)]
55pub enum PlanError {
56 #[error("predicate validation failed: {0}")]
57 PredicateInvalid(Box<predicate::ValidateError>),
58
59 #[error("{0}")]
60 Order(Box<OrderPlanError>),
61
62 #[error("{0}")]
63 Access(Box<AccessPlanError>),
64
65 #[error("{0}")]
66 Policy(Box<PolicyPlanError>),
67
68 #[error("{0}")]
69 Cursor(Box<CursorPlanError>),
70}
71
72#[derive(Debug, ThisError)]
78pub enum OrderPlanError {
79 #[error("unknown order field '{field}'")]
81 UnknownField { field: String },
82
83 #[error("order field '{field}' is not orderable")]
85 UnorderableField { field: String },
86
87 #[error("order field '{field}' appears multiple times")]
89 DuplicateOrderField { field: String },
90
91 #[error("order specification must end with primary key '{field}' as deterministic tie-break")]
93 MissingPrimaryKeyTieBreak { field: String },
94}
95
96#[derive(Debug, ThisError)]
102pub enum AccessPlanError {
103 #[error("index '{index}' not found on entity")]
105 IndexNotFound { index: IndexModel },
106
107 #[error("index prefix length {prefix_len} exceeds index field count {field_len}")]
109 IndexPrefixTooLong { prefix_len: usize, field_len: usize },
110
111 #[error("index prefix must include at least one value")]
113 IndexPrefixEmpty,
114
115 #[error("index prefix value for field '{field}' is incompatible")]
117 IndexPrefixValueMismatch { field: String },
118
119 #[error("primary key field '{field}' is not key-compatible")]
121 PrimaryKeyNotKeyable { field: String },
122
123 #[error("key '{key:?}' is incompatible with primary key '{field}'")]
125 PrimaryKeyMismatch { field: String, key: Value },
126
127 #[error("key range start is greater than end")]
129 InvalidKeyRange,
130}
131
132#[derive(Clone, Debug, Eq, PartialEq, ThisError)]
138pub enum PolicyPlanError {
139 #[error("order specification must include at least one field")]
141 EmptyOrderSpec,
142
143 #[error("delete plans must not include pagination")]
145 DeletePlanWithPagination,
146
147 #[error("load plans must not include delete limits")]
149 LoadPlanWithDeleteLimit,
150
151 #[error("delete limit requires an explicit ordering")]
153 DeleteLimitRequiresOrder,
154
155 #[error(
157 "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."
158 )]
159 UnorderedPagination,
160}
161
162impl From<PlanPolicyError> for PolicyPlanError {
163 fn from(err: PlanPolicyError) -> Self {
164 match err {
165 PlanPolicyError::EmptyOrderSpec => Self::EmptyOrderSpec,
166 PlanPolicyError::DeletePlanWithPagination => Self::DeletePlanWithPagination,
167 PlanPolicyError::LoadPlanWithDeleteLimit => Self::LoadPlanWithDeleteLimit,
168 PlanPolicyError::DeleteLimitRequiresOrder => Self::DeleteLimitRequiresOrder,
169 PlanPolicyError::UnorderedPagination => Self::UnorderedPagination,
170 }
171 }
172}
173
174impl From<predicate::ValidateError> for PlanError {
175 fn from(err: predicate::ValidateError) -> Self {
176 Self::PredicateInvalid(Box::new(err))
177 }
178}
179
180impl From<OrderPlanError> for PlanError {
181 fn from(err: OrderPlanError) -> Self {
182 Self::Order(Box::new(err))
183 }
184}
185
186impl From<AccessPlanError> for PlanError {
187 fn from(err: AccessPlanError) -> Self {
188 Self::Access(Box::new(err))
189 }
190}
191
192impl From<PolicyPlanError> for PlanError {
193 fn from(err: PolicyPlanError) -> Self {
194 Self::Policy(Box::new(err))
195 }
196}
197
198impl From<CursorPlanError> for PlanError {
199 fn from(err: CursorPlanError) -> Self {
200 Self::Cursor(Box::new(err))
201 }
202}
203
204impl From<PlanPolicyError> for PlanError {
205 fn from(err: PlanPolicyError) -> Self {
206 Self::from(PolicyPlanError::from(err))
207 }
208}
209
210pub(crate) fn validate_logical_plan_model(
219 schema: &SchemaInfo,
220 model: &EntityModel,
221 plan: &AccessPlannedQuery<Value>,
222) -> Result<(), PlanError> {
223 validate_plan_core(
224 schema,
225 model,
226 plan,
227 validate_order,
228 |schema, model, plan| validate_access_plan_model(schema, model, &plan.access),
229 )?;
230
231 Ok(())
232}
233
234pub(crate) fn validate_executor_plan<E: EntityKind>(
243 plan: &AccessPlannedQuery<E::Key>,
244) -> Result<(), InternalError> {
245 let schema = SchemaInfo::from_entity_model(E::MODEL).map_err(|err| {
246 InternalError::query_invariant(format!("entity schema invalid for {}: {err}", E::PATH))
247 })?;
248
249 validate_access_plan(&schema, E::MODEL, &plan.access)
250 .map_err(InternalError::from_executor_plan_error)?;
251
252 Ok(())
253}
254
255fn validate_plan_core<K, FOrder, FAccess>(
257 schema: &SchemaInfo,
258 model: &EntityModel,
259 plan: &AccessPlannedQuery<K>,
260 validate_order_fn: FOrder,
261 validate_access_fn: FAccess,
262) -> Result<(), PlanError>
263where
264 FOrder: Fn(&SchemaInfo, &crate::db::query::plan::OrderSpec) -> Result<(), PlanError>,
265 FAccess: Fn(&SchemaInfo, &EntityModel, &AccessPlannedQuery<K>) -> Result<(), PlanError>,
266{
267 if let Some(predicate) = &plan.predicate {
268 predicate::validate(schema, predicate)?;
269 }
270
271 if let Some(order) = &plan.order {
272 validate_order_fn(schema, order)?;
273 validate_no_duplicate_non_pk_order_fields(model, order)?;
274 validate_primary_key_tie_break(model, order)?;
275 }
276
277 validate_access_fn(schema, model, plan)?;
278 semantics::validate_plan_semantics(plan)?;
279
280 Ok(())
281}