icydb_core/db/query/plan/validate/
mod.rs1mod access;
11mod order;
12mod pushdown;
13mod semantics;
14
15#[cfg(test)]
16mod tests;
17
18use crate::{
19 db::{
20 cursor::CursorPlanError,
21 plan::{AccessPlannedQuery, OrderSpec},
22 policy::PlanPolicyError,
23 query::predicate::{self, SchemaInfo},
24 },
25 model::{entity::EntityModel, index::IndexModel},
26 value::Value,
27};
28use thiserror::Error as ThisError;
29
30pub(crate) use access::{validate_access_plan, validate_access_plan_model};
32pub(crate) use order::{
33 validate_no_duplicate_non_pk_order_fields, validate_order, validate_primary_key_tie_break,
34};
35#[cfg(test)]
36pub(crate) use pushdown::assess_secondary_order_pushdown_if_applicable;
37#[cfg(test)]
38pub(crate) use pushdown::{
39 PushdownApplicability, assess_secondary_order_pushdown_if_applicable_validated,
40};
41pub(crate) use pushdown::{
42 PushdownSurfaceEligibility, SecondaryOrderPushdownEligibility, SecondaryOrderPushdownRejection,
43 assess_secondary_order_pushdown,
44};
45
46#[derive(Debug, ThisError)]
56pub enum PlanError {
57 #[error("predicate validation failed: {0}")]
58 PredicateInvalid(Box<predicate::ValidateError>),
59
60 #[error("{0}")]
61 Order(Box<OrderPlanError>),
62
63 #[error("{0}")]
64 Access(Box<AccessPlanError>),
65
66 #[error("{0}")]
67 Policy(Box<PolicyPlanError>),
68
69 #[error("{0}")]
70 Cursor(Box<CursorPlanError>),
71}
72
73#[derive(Debug, ThisError)]
79pub enum OrderPlanError {
80 #[error("unknown order field '{field}'")]
82 UnknownField { field: String },
83
84 #[error("order field '{field}' is not orderable")]
86 UnorderableField { field: String },
87
88 #[error("order field '{field}' appears multiple times")]
90 DuplicateOrderField { field: String },
91
92 #[error("order specification must end with primary key '{field}' as deterministic tie-break")]
94 MissingPrimaryKeyTieBreak { field: String },
95}
96
97#[derive(Debug, ThisError)]
103pub enum AccessPlanError {
104 #[error("index '{index}' not found on entity")]
106 IndexNotFound { index: IndexModel },
107
108 #[error("index prefix length {prefix_len} exceeds index field count {field_len}")]
110 IndexPrefixTooLong { prefix_len: usize, field_len: usize },
111
112 #[error("index prefix must include at least one value")]
114 IndexPrefixEmpty,
115
116 #[error("index prefix value for field '{field}' is incompatible")]
118 IndexPrefixValueMismatch { field: String },
119
120 #[error("primary key field '{field}' is not key-compatible")]
122 PrimaryKeyNotKeyable { field: String },
123
124 #[error("key '{key:?}' is incompatible with primary key '{field}'")]
126 PrimaryKeyMismatch { field: String, key: Value },
127
128 #[error("key range start is greater than end")]
130 InvalidKeyRange,
131}
132
133#[derive(Clone, Debug, Eq, PartialEq, ThisError)]
139pub enum PolicyPlanError {
140 #[error("order specification must include at least one field")]
142 EmptyOrderSpec,
143
144 #[error("delete plans must not include pagination")]
146 DeletePlanWithPagination,
147
148 #[error("load plans must not include delete limits")]
150 LoadPlanWithDeleteLimit,
151
152 #[error("delete limit requires an explicit ordering")]
154 DeleteLimitRequiresOrder,
155
156 #[error(
158 "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."
159 )]
160 UnorderedPagination,
161}
162
163impl From<PlanPolicyError> for PolicyPlanError {
164 fn from(err: PlanPolicyError) -> Self {
165 match err {
166 PlanPolicyError::EmptyOrderSpec => Self::EmptyOrderSpec,
167 PlanPolicyError::DeletePlanWithPagination => Self::DeletePlanWithPagination,
168 PlanPolicyError::LoadPlanWithDeleteLimit => Self::LoadPlanWithDeleteLimit,
169 PlanPolicyError::DeleteLimitRequiresOrder => Self::DeleteLimitRequiresOrder,
170 PlanPolicyError::UnorderedPagination => Self::UnorderedPagination,
171 }
172 }
173}
174
175impl From<predicate::ValidateError> for PlanError {
176 fn from(err: predicate::ValidateError) -> Self {
177 Self::PredicateInvalid(Box::new(err))
178 }
179}
180
181impl From<OrderPlanError> for PlanError {
182 fn from(err: OrderPlanError) -> Self {
183 Self::Order(Box::new(err))
184 }
185}
186
187impl From<AccessPlanError> for PlanError {
188 fn from(err: AccessPlanError) -> Self {
189 Self::Access(Box::new(err))
190 }
191}
192
193impl From<PolicyPlanError> for PlanError {
194 fn from(err: PolicyPlanError) -> Self {
195 Self::Policy(Box::new(err))
196 }
197}
198
199impl From<CursorPlanError> for PlanError {
200 fn from(err: CursorPlanError) -> Self {
201 Self::Cursor(Box::new(err))
202 }
203}
204
205impl From<PlanPolicyError> for PlanError {
206 fn from(err: PlanPolicyError) -> Self {
207 Self::from(PolicyPlanError::from(err))
208 }
209}
210
211pub(crate) fn validate_logical_plan_model(
220 schema: &SchemaInfo,
221 model: &EntityModel,
222 plan: &AccessPlannedQuery<Value>,
223) -> Result<(), PlanError> {
224 validate_plan_core(
225 schema,
226 model,
227 plan,
228 validate_order,
229 |schema, model, plan| validate_access_plan_model(schema, model, &plan.access),
230 )?;
231
232 Ok(())
233}
234
235fn validate_plan_core<K, FOrder, FAccess>(
237 schema: &SchemaInfo,
238 model: &EntityModel,
239 plan: &AccessPlannedQuery<K>,
240 validate_order_fn: FOrder,
241 validate_access_fn: FAccess,
242) -> Result<(), PlanError>
243where
244 FOrder: Fn(&SchemaInfo, &OrderSpec) -> Result<(), PlanError>,
245 FAccess: Fn(&SchemaInfo, &EntityModel, &AccessPlannedQuery<K>) -> Result<(), PlanError>,
246{
247 if let Some(predicate) = &plan.predicate {
248 predicate::validate(schema, predicate)?;
249 }
250
251 if let Some(order) = &plan.order {
252 validate_order_fn(schema, order)?;
253 validate_no_duplicate_non_pk_order_fields(model, order)?;
254 validate_primary_key_tie_break(model, order)?;
255 }
256
257 validate_access_fn(schema, model, plan)?;
258 semantics::validate_plan_semantics(plan)?;
259
260 Ok(())
261}