icydb_core/db/query/plan/validate/
mod.rs1mod order;
11mod pushdown;
12mod semantics;
13
14#[cfg(test)]
15mod tests;
16
17use crate::{
18 db::{
19 access::validate_access_plan_model as validate_access_plan_model_shared,
20 cursor::CursorPlanError,
21 plan::{AccessPlannedQuery, OrderSpec},
22 policy::PlanPolicyError,
23 query::predicate::{self, SchemaInfo},
24 },
25 model::entity::EntityModel,
26 value::Value,
27};
28use thiserror::Error as ThisError;
29
30pub(crate) use crate::db::access::AccessPlanError;
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(Clone, Debug, Eq, PartialEq, ThisError)]
103pub enum PolicyPlanError {
104 #[error("order specification must include at least one field")]
106 EmptyOrderSpec,
107
108 #[error("delete plans must not include pagination")]
110 DeletePlanWithPagination,
111
112 #[error("load plans must not include delete limits")]
114 LoadPlanWithDeleteLimit,
115
116 #[error("delete limit requires an explicit ordering")]
118 DeleteLimitRequiresOrder,
119
120 #[error(
122 "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."
123 )]
124 UnorderedPagination,
125}
126
127impl From<PlanPolicyError> for PolicyPlanError {
128 fn from(err: PlanPolicyError) -> Self {
129 match err {
130 PlanPolicyError::EmptyOrderSpec => Self::EmptyOrderSpec,
131 PlanPolicyError::DeletePlanWithPagination => Self::DeletePlanWithPagination,
132 PlanPolicyError::LoadPlanWithDeleteLimit => Self::LoadPlanWithDeleteLimit,
133 PlanPolicyError::DeleteLimitRequiresOrder => Self::DeleteLimitRequiresOrder,
134 PlanPolicyError::UnorderedPagination => Self::UnorderedPagination,
135 }
136 }
137}
138
139impl From<predicate::ValidateError> for PlanError {
140 fn from(err: predicate::ValidateError) -> Self {
141 Self::PredicateInvalid(Box::new(err))
142 }
143}
144
145impl From<OrderPlanError> for PlanError {
146 fn from(err: OrderPlanError) -> Self {
147 Self::Order(Box::new(err))
148 }
149}
150
151impl From<AccessPlanError> for PlanError {
152 fn from(err: AccessPlanError) -> Self {
153 Self::Access(Box::new(err))
154 }
155}
156
157impl From<PolicyPlanError> for PlanError {
158 fn from(err: PolicyPlanError) -> Self {
159 Self::Policy(Box::new(err))
160 }
161}
162
163impl From<CursorPlanError> for PlanError {
164 fn from(err: CursorPlanError) -> Self {
165 Self::Cursor(Box::new(err))
166 }
167}
168
169impl From<PlanPolicyError> for PlanError {
170 fn from(err: PlanPolicyError) -> Self {
171 Self::from(PolicyPlanError::from(err))
172 }
173}
174
175pub(crate) fn validate_logical_plan_model(
184 schema: &SchemaInfo,
185 model: &EntityModel,
186 plan: &AccessPlannedQuery<Value>,
187) -> Result<(), PlanError> {
188 validate_plan_core(
189 schema,
190 model,
191 plan,
192 validate_order,
193 |schema, model, plan| {
194 validate_access_plan_model_shared(schema, model, &plan.access).map_err(PlanError::from)
195 },
196 )?;
197
198 Ok(())
199}
200
201fn validate_plan_core<K, FOrder, FAccess>(
203 schema: &SchemaInfo,
204 model: &EntityModel,
205 plan: &AccessPlannedQuery<K>,
206 validate_order_fn: FOrder,
207 validate_access_fn: FAccess,
208) -> Result<(), PlanError>
209where
210 FOrder: Fn(&SchemaInfo, &OrderSpec) -> Result<(), PlanError>,
211 FAccess: Fn(&SchemaInfo, &EntityModel, &AccessPlannedQuery<K>) -> Result<(), PlanError>,
212{
213 if let Some(predicate) = &plan.predicate {
214 predicate::validate(schema, predicate)?;
215 }
216
217 if let Some(order) = &plan.order {
218 validate_order_fn(schema, order)?;
219 validate_no_duplicate_non_pk_order_fields(model, order)?;
220 validate_primary_key_tie_break(model, order)?;
221 }
222
223 validate_access_fn(schema, model, plan)?;
224 semantics::validate_plan_semantics(plan)?;
225
226 Ok(())
227}