1use crate::{
2 db::query::{
3 ReadConsistency,
4 plan::{
5 DeleteLimitSpec, ExecutablePlan, ExplainPlan, LogicalPlan, OrderDirection, OrderSpec,
6 PageSpec, PlanError, ProjectionSpec,
7 planner::{PlannerError, plan_access},
8 validate::validate_access_plan,
9 validate::validate_order,
10 },
11 predicate::{Predicate, SchemaInfo, ValidateError, normalize},
12 },
13 error::InternalError,
14 traits::EntityKind,
15};
16use std::marker::PhantomData;
17use thiserror::Error as ThisError;
18
19#[derive(Clone, Copy, Debug, Eq, PartialEq)]
25pub enum QueryMode {
26 Load,
27 Delete,
28}
29
30#[derive(Clone, Copy, Debug, Eq, PartialEq)]
37pub struct DeleteLimit {
38 pub max_rows: u32,
39}
40
41impl DeleteLimit {
42 #[must_use]
44 pub const fn new(max_rows: u32) -> Self {
45 Self { max_rows }
46 }
47
48 pub(crate) const fn to_spec(self) -> DeleteLimitSpec {
49 DeleteLimitSpec {
50 max_rows: self.max_rows,
51 }
52 }
53}
54
55#[derive(Debug)]
67pub struct Query<E: EntityKind> {
68 mode: QueryMode,
69 predicate: Option<Predicate>,
70 order: Option<OrderSpec>,
71 delete_limit: Option<DeleteLimit>,
72 page: Option<Page>,
73 projection: ProjectionSpec,
74 consistency: ReadConsistency,
75 _marker: PhantomData<E>,
76}
77
78#[derive(Clone, Copy, Debug, Eq, PartialEq)]
85pub struct Page {
86 pub limit: u32,
87 pub offset: u64,
88}
89
90impl Page {
91 #[must_use]
93 pub const fn new(limit: u32, offset: u64) -> Self {
94 Self { limit, offset }
95 }
96
97 pub(crate) const fn to_spec(self) -> PageSpec {
98 PageSpec {
99 limit: Some(self.limit),
100 offset: self.offset,
101 }
102 }
103}
104
105impl<E: EntityKind> Query<E> {
106 #[must_use]
110 pub const fn new(consistency: ReadConsistency) -> Self {
111 Self {
112 mode: QueryMode::Load,
113 predicate: None,
114 order: None,
115 delete_limit: None,
116 page: None,
117 projection: ProjectionSpec::All,
118 consistency,
119 _marker: PhantomData,
120 }
121 }
122
123 #[must_use]
125 pub fn filter(mut self, predicate: Predicate) -> Self {
126 self.predicate = match self.predicate.take() {
127 Some(existing) => Some(Predicate::And(vec![existing, predicate])),
128 None => Some(predicate),
129 };
130 self
131 }
132
133 #[must_use]
135 pub fn order_by(mut self, field: &'static str) -> Self {
136 self.order = Some(push_order(self.order, field, OrderDirection::Asc));
137 self
138 }
139
140 #[must_use]
142 pub fn order_by_desc(mut self, field: &'static str) -> Self {
143 self.order = Some(push_order(self.order, field, OrderDirection::Desc));
144 self
145 }
146
147 #[must_use]
149 pub const fn delete(mut self) -> Self {
150 self.mode = QueryMode::Delete;
151 self
152 }
153
154 #[must_use]
156 pub const fn delete_limit(mut self, max_rows: u32) -> Self {
157 self.delete_limit = Some(DeleteLimit::new(max_rows));
158 self
159 }
160
161 #[must_use]
164 pub const fn page(mut self, limit: u32, offset: u64) -> Self {
165 self.page = Some(Page::new(limit, offset));
166 self
167 }
168
169 pub fn explain(&self) -> Result<ExplainPlan, QueryError> {
171 let plan = self.build_plan::<E>()?;
172 Ok(plan.explain())
173 }
174
175 pub fn plan(&self) -> Result<ExecutablePlan<E>, QueryError> {
177 let plan = self.build_plan::<E>()?;
178 Ok(ExecutablePlan::new(plan))
179 }
180
181 fn build_plan<T: EntityKind>(&self) -> Result<LogicalPlan, QueryError> {
182 let model = T::MODEL;
184 let schema_info = SchemaInfo::from_entity_model(model)?;
185 self.validate_intent()?;
186
187 if let Some(order) = &self.order {
188 validate_order(&schema_info, order)?;
189 }
190
191 let normalized_predicate = self.predicate.as_ref().map(normalize);
193 let access_plan = plan_access::<T>(&schema_info, normalized_predicate.as_ref())?;
194
195 validate_access_plan(&schema_info, model, &access_plan)?;
196
197 let plan = LogicalPlan {
199 mode: self.mode,
200 access: access_plan,
201 predicate: normalized_predicate,
202 order: self.order.clone(),
203 delete_limit: self.delete_limit.map(DeleteLimit::to_spec),
204 page: self.page.map(Page::to_spec),
205 projection: self.projection.clone(),
206 consistency: self.consistency,
207 };
208
209 Ok(plan)
210 }
211
212 const fn validate_intent(&self) -> Result<(), IntentError> {
214 match self.mode {
215 QueryMode::Load => {
216 if self.delete_limit.is_some() {
217 return Err(IntentError::DeleteLimitOnLoad);
218 }
219 }
220 QueryMode::Delete => {
221 if self.page.is_some() && self.delete_limit.is_some() {
222 return Err(IntentError::DeleteLimitWithPagination);
223 }
224 if self.page.is_some() {
225 return Err(IntentError::DeletePaginationNotSupported);
226 }
227 if self.delete_limit.is_some() && self.order.is_none() {
228 return Err(IntentError::DeleteLimitRequiresOrder);
229 }
230 }
231 }
232
233 Ok(())
234 }
235}
236
237#[derive(Debug, ThisError)]
241pub enum QueryError {
242 #[error("{0}")]
243 Validate(#[from] ValidateError),
244 #[error("{0}")]
245 Plan(#[from] PlanError),
246 #[error("{0}")]
247 Intent(#[from] IntentError),
248 #[error("{0}")]
249 Execute(#[from] InternalError),
250}
251
252impl From<PlannerError> for QueryError {
253 fn from(err: PlannerError) -> Self {
254 match err {
255 PlannerError::Plan(err) => Self::Plan(err),
256 PlannerError::Internal(err) => Self::Execute(err),
257 }
258 }
259}
260
261#[derive(Debug, ThisError)]
265pub enum IntentError {
266 #[error("delete limit is only valid for delete intents")]
267 DeleteLimitOnLoad,
268 #[error("delete queries do not support pagination offsets")]
269 DeletePaginationNotSupported,
270 #[error("delete limit cannot be combined with pagination")]
271 DeleteLimitWithPagination,
272 #[error("delete limit requires an explicit ordering")]
273 DeleteLimitRequiresOrder,
274}
275
276fn push_order(
278 order: Option<OrderSpec>,
279 field: &'static str,
280 direction: OrderDirection,
281) -> OrderSpec {
282 match order {
283 Some(mut spec) => {
284 spec.fields.push((field.to_string(), direction));
285 spec
286 }
287 None => OrderSpec {
288 fields: vec![(field.to_string(), direction)],
289 },
290 }
291}
292
293#[cfg(test)]
294mod tests;