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;
39#[cfg(test)]
40pub(crate) use pushdown::{
41 PushdownApplicability, assess_secondary_order_pushdown_if_applicable_validated,
42};
43pub(crate) use pushdown::{
44 PushdownSurfaceEligibility, SecondaryOrderPushdownEligibility, SecondaryOrderPushdownRejection,
45 assess_secondary_order_pushdown,
46};
47
48#[derive(Debug, ThisError)]
58pub enum PlanError {
59 #[error("predicate validation failed: {0}")]
60 PredicateInvalid(Box<predicate::ValidateError>),
61
62 #[error("{0}")]
63 Order(Box<OrderPlanError>),
64
65 #[error("{0}")]
66 Access(Box<AccessPlanError>),
67
68 #[error("{0}")]
69 Policy(Box<PolicyPlanError>),
70
71 #[error("{0}")]
72 Cursor(Box<CursorPlanError>),
73}
74
75#[derive(Debug, ThisError)]
81pub enum OrderPlanError {
82 #[error("unknown order field '{field}'")]
84 UnknownField { field: String },
85
86 #[error("order field '{field}' is not orderable")]
88 UnorderableField { field: String },
89
90 #[error("order field '{field}' appears multiple times")]
92 DuplicateOrderField { field: String },
93
94 #[error("order specification must end with primary key '{field}' as deterministic tie-break")]
96 MissingPrimaryKeyTieBreak { field: String },
97}
98
99#[derive(Debug, ThisError)]
105pub enum AccessPlanError {
106 #[error("index '{index}' not found on entity")]
108 IndexNotFound { index: IndexModel },
109
110 #[error("index prefix length {prefix_len} exceeds index field count {field_len}")]
112 IndexPrefixTooLong { prefix_len: usize, field_len: usize },
113
114 #[error("index prefix must include at least one value")]
116 IndexPrefixEmpty,
117
118 #[error("index prefix value for field '{field}' is incompatible")]
120 IndexPrefixValueMismatch { field: String },
121
122 #[error("primary key field '{field}' is not key-compatible")]
124 PrimaryKeyNotKeyable { field: String },
125
126 #[error("key '{key:?}' is incompatible with primary key '{field}'")]
128 PrimaryKeyMismatch { field: String, key: Value },
129
130 #[error("key range start is greater than end")]
132 InvalidKeyRange,
133}
134
135#[derive(Clone, Debug, Eq, PartialEq, ThisError)]
141pub enum PolicyPlanError {
142 #[error("order specification must include at least one field")]
144 EmptyOrderSpec,
145
146 #[error("delete plans must not include pagination")]
148 DeletePlanWithPagination,
149
150 #[error("load plans must not include delete limits")]
152 LoadPlanWithDeleteLimit,
153
154 #[error("delete limit requires an explicit ordering")]
156 DeleteLimitRequiresOrder,
157
158 #[error(
160 "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."
161 )]
162 UnorderedPagination,
163}
164
165impl From<PlanPolicyError> for PolicyPlanError {
166 fn from(err: PlanPolicyError) -> Self {
167 match err {
168 PlanPolicyError::EmptyOrderSpec => Self::EmptyOrderSpec,
169 PlanPolicyError::DeletePlanWithPagination => Self::DeletePlanWithPagination,
170 PlanPolicyError::LoadPlanWithDeleteLimit => Self::LoadPlanWithDeleteLimit,
171 PlanPolicyError::DeleteLimitRequiresOrder => Self::DeleteLimitRequiresOrder,
172 PlanPolicyError::UnorderedPagination => Self::UnorderedPagination,
173 }
174 }
175}
176
177impl From<predicate::ValidateError> for PlanError {
178 fn from(err: predicate::ValidateError) -> Self {
179 Self::PredicateInvalid(Box::new(err))
180 }
181}
182
183impl From<OrderPlanError> for PlanError {
184 fn from(err: OrderPlanError) -> Self {
185 Self::Order(Box::new(err))
186 }
187}
188
189impl From<AccessPlanError> for PlanError {
190 fn from(err: AccessPlanError) -> Self {
191 Self::Access(Box::new(err))
192 }
193}
194
195impl From<PolicyPlanError> for PlanError {
196 fn from(err: PolicyPlanError) -> Self {
197 Self::Policy(Box::new(err))
198 }
199}
200
201impl From<CursorPlanError> for PlanError {
202 fn from(err: CursorPlanError) -> Self {
203 Self::Cursor(Box::new(err))
204 }
205}
206
207impl From<PlanPolicyError> for PlanError {
208 fn from(err: PlanPolicyError) -> Self {
209 Self::from(PolicyPlanError::from(err))
210 }
211}
212
213pub(crate) fn validate_logical_plan_model(
222 schema: &SchemaInfo,
223 model: &EntityModel,
224 plan: &AccessPlannedQuery<Value>,
225) -> Result<(), PlanError> {
226 validate_plan_core(
227 schema,
228 model,
229 plan,
230 validate_order,
231 |schema, model, plan| validate_access_plan_model(schema, model, &plan.access),
232 )?;
233
234 Ok(())
235}
236
237pub(crate) fn validate_executor_plan<E: EntityKind>(
246 plan: &AccessPlannedQuery<E::Key>,
247) -> Result<(), InternalError> {
248 let schema = SchemaInfo::from_entity_model(E::MODEL).map_err(|err| {
249 InternalError::query_invariant(format!("entity schema invalid for {}: {err}", E::PATH))
250 })?;
251
252 validate_access_plan(&schema, E::MODEL, &plan.access)
253 .map_err(InternalError::from_executor_plan_error)?;
254
255 Ok(())
256}
257
258fn validate_plan_core<K, FOrder, FAccess>(
260 schema: &SchemaInfo,
261 model: &EntityModel,
262 plan: &AccessPlannedQuery<K>,
263 validate_order_fn: FOrder,
264 validate_access_fn: FAccess,
265) -> Result<(), PlanError>
266where
267 FOrder: Fn(&SchemaInfo, &crate::db::query::plan::OrderSpec) -> Result<(), PlanError>,
268 FAccess: Fn(&SchemaInfo, &EntityModel, &AccessPlannedQuery<K>) -> Result<(), PlanError>,
269{
270 if let Some(predicate) = &plan.predicate {
271 predicate::validate(schema, predicate)?;
272 }
273
274 if let Some(order) = &plan.order {
275 validate_order_fn(schema, order)?;
276 validate_no_duplicate_non_pk_order_fields(model, order)?;
277 validate_primary_key_tie_break(model, order)?;
278 }
279
280 validate_access_fn(schema, model, plan)?;
281 semantics::validate_plan_semantics(plan)?;
282
283 Ok(())
284}