1use crate::capabilities::{BackendCapabilities, Feature};
10use crate::error::BackendCapabilityError;
11use crate::expr::path_alias_of;
12use crate::expr::{AggFunc, Aggregate, Column, Expr, Ident, Lookup, RelatedColumn, WindowFunc};
13
14#[derive(Debug, Clone, PartialEq)]
16pub struct QuerySource {
17 pub name: Ident,
19 pub alias: Option<Ident>,
21 pub subquery: Option<Box<QueryPlan>>,
23}
24
25impl QuerySource {
26 pub fn table(name: impl Into<Ident>) -> Self {
28 Self {
29 name: name.into(),
30 alias: None,
31 subquery: None,
32 }
33 }
34
35 pub fn subquery(plan: QueryPlan, alias: impl Into<Ident>) -> Self {
37 Self {
38 name: alias.into(),
39 alias: None,
40 subquery: Some(Box::new(plan)),
41 }
42 }
43
44 pub fn reference(&self) -> &Ident {
46 self.alias.as_ref().unwrap_or(&self.name)
47 }
48}
49
50#[derive(Debug, Clone, PartialEq)]
52pub struct SelectExpr {
53 pub expr: Expr,
55 pub alias: Option<Ident>,
57}
58
59impl SelectExpr {
60 pub fn new(expr: Expr, alias: Option<Ident>) -> Self {
62 Self { expr, alias }
63 }
64}
65
66impl From<&'static str> for SelectExpr {
68 fn from(column: &'static str) -> Self {
69 Self::new(Expr::col(column), Some(column.into()))
70 }
71}
72
73impl<N: Into<Ident>, E: Into<Expr>> From<(N, E)> for SelectExpr {
75 fn from((name, expr): (N, E)) -> Self {
76 Self::new(expr.into(), Some(name.into()))
77 }
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub enum JoinKind {
83 Inner,
85 Left,
87}
88
89#[derive(Debug, Clone, PartialEq)]
91pub struct JoinExpr {
92 pub kind: JoinKind,
94 pub source: QuerySource,
96 pub on: Expr,
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102pub enum OrderDirection {
103 Asc,
105 Desc,
107}
108
109#[derive(Debug, Clone, PartialEq)]
111pub struct OrderExpr {
112 pub expr: Expr,
114 pub direction: OrderDirection,
116}
117
118#[derive(Debug, Clone, PartialEq, Default)]
120pub enum DistinctMode {
121 #[default]
123 None,
124 All,
126 On(Vec<Expr>),
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
132pub enum LockMode {
133 ForUpdate,
135 ForUpdateNoWait,
137 ForUpdateSkipLocked,
139}
140
141#[derive(Debug, Clone, Copy, PartialEq, Eq)]
143pub enum SetOp {
144 Union,
146 UnionAll,
148 Intersect,
150 Except,
152}
153
154#[derive(Debug, Clone, PartialEq)]
156pub struct Compound {
157 pub op: SetOp,
159 pub plan: QueryPlan,
161}
162
163#[derive(Debug, Clone, PartialEq)]
168pub struct QueryPlan {
169 pub source: QuerySource,
171 pub projection: Vec<SelectExpr>,
173 pub joins: Vec<JoinExpr>,
175 pub filter: Option<Expr>,
177 pub grouping: Vec<Expr>,
179 pub having: Option<Expr>,
181 pub ordering: Vec<OrderExpr>,
183 pub limit: Option<u64>,
185 pub offset: Option<u64>,
187 pub distinct: DistinctMode,
189 pub lock: Option<LockMode>,
191 pub compound: Vec<Compound>,
195 pub origin: Option<PlanOrigin>,
199}
200
201#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
203pub struct PlanOrigin(usize);
204
205impl PlanOrigin {
206 pub(crate) fn new(id: usize) -> Self {
207 Self(id)
208 }
209}
210
211impl QueryPlan {
212 pub fn from_table(table: impl Into<Ident>) -> Self {
214 Self {
215 source: QuerySource::table(table),
216 projection: Vec::new(),
217 joins: Vec::new(),
218 filter: None,
219 grouping: Vec::new(),
220 having: None,
221 ordering: Vec::new(),
222 limit: None,
223 offset: None,
224 distinct: DistinctMode::None,
225 lock: None,
226 compound: Vec::new(),
227 origin: None,
228 }
229 }
230
231 pub fn from_subquery(inner: QueryPlan, alias: impl Into<Ident>) -> Self {
233 let mut plan = Self::from_table("");
234 plan.source = QuerySource::subquery(inner, alias);
235 plan
236 }
237
238 #[must_use]
240 pub fn select(mut self, expr: impl Into<Expr>, alias: Option<&'static str>) -> Self {
241 self.projection.push(SelectExpr {
242 expr: expr.into(),
243 alias: alias.map(Into::into),
244 });
245 self
246 }
247
248 #[must_use]
250 pub fn filter(mut self, predicate: Expr) -> Self {
251 self.filter = Some(match self.filter.take() {
252 Some(existing) => existing.and(predicate),
253 None => predicate,
254 });
255 self
256 }
257
258 #[must_use]
260 pub fn exclude(self, predicate: Expr) -> Self {
261 self.filter(!predicate)
262 }
263
264 #[must_use]
266 pub fn order_by(mut self, expr: impl Into<Expr>, direction: OrderDirection) -> Self {
267 self.ordering.push(OrderExpr {
268 expr: expr.into(),
269 direction,
270 });
271 self
272 }
273
274 #[must_use]
276 pub fn limit(mut self, n: u64) -> Self {
277 self.limit = Some(n);
278 self
279 }
280
281 #[must_use]
283 pub fn offset(mut self, n: u64) -> Self {
284 self.offset = Some(n);
285 self
286 }
287
288 #[must_use]
290 pub fn join(mut self, kind: JoinKind, source: QuerySource, on: Expr) -> Self {
291 self.joins.push(JoinExpr { kind, source, on });
292 self
293 }
294
295 #[must_use]
297 pub fn lock(mut self, mode: LockMode) -> Self {
298 self.lock = Some(mode);
299 self
300 }
301
302 #[must_use]
304 pub fn distinct(mut self, mode: DistinctMode) -> Self {
305 self.distinct = mode;
306 self
307 }
308
309 fn exprs(&self) -> impl Iterator<Item = &Expr> {
311 let distinct_on = match &self.distinct {
312 DistinctMode::On(exprs) => exprs.as_slice(),
313 _ => &[],
314 };
315 self.projection
316 .iter()
317 .map(|s| &s.expr)
318 .chain(self.joins.iter().map(|j| &j.on))
319 .chain(self.filter.iter())
320 .chain(self.having.iter())
321 .chain(self.grouping.iter())
322 .chain(self.ordering.iter().map(|o| &o.expr))
323 .chain(distinct_on)
324 }
325
326 pub fn required_features(&self) -> Vec<Feature> {
328 let mut out = Vec::new();
329 let mut need = |f| {
330 if !out.contains(&f) {
331 out.push(f);
332 }
333 };
334 if !self.joins.is_empty() {
335 need(Feature::Joins);
336 }
337 match self.lock {
338 Some(LockMode::ForUpdate) => need(Feature::RowLocking),
339 Some(_) => {
340 need(Feature::RowLocking);
341 need(Feature::LockModifiers);
342 }
343 None => {}
344 }
345 if matches!(self.distinct, DistinctMode::On(_)) {
346 need(Feature::DistinctOn);
347 }
348 if !self.compound.is_empty() {
349 need(Feature::SetOperations);
350 }
351 let nested = self
352 .compound
353 .iter()
354 .map(|c| &c.plan)
355 .chain(self.source.subquery.as_deref());
356 for plan in nested {
357 plan.required_features().into_iter().for_each(&mut need);
358 }
359 for e in self.exprs() {
360 visit(e, &mut need);
361 }
362 out
363 }
364
365 #[must_use]
369 pub fn resolve_relations(mut self) -> Self {
370 let root = self.source.reference().clone();
371 let mut joins = std::mem::take(&mut self.joins);
372 for i in 0..joins.len() {
374 let mut on = std::mem::replace(&mut joins[i].on, Expr::Or(Vec::new()));
375 resolve_expr(&mut on, &root, &mut joins);
376 joins[i].on = on;
377 }
378 let mut exprs: Vec<&mut Expr> = Vec::new();
379 exprs.extend(self.projection.iter_mut().map(|s| &mut s.expr));
380 exprs.extend(self.filter.iter_mut());
381 exprs.extend(self.having.iter_mut());
382 exprs.extend(self.grouping.iter_mut());
383 exprs.extend(self.ordering.iter_mut().map(|o| &mut o.expr));
384 if let DistinctMode::On(on) = &mut self.distinct {
385 exprs.extend(on.iter_mut());
386 }
387 for e in exprs {
388 resolve_expr(e, &root, &mut joins);
389 }
390 self.joins = joins;
391 self.compound = std::mem::take(&mut self.compound)
392 .into_iter()
393 .map(|c| Compound {
394 op: c.op,
395 plan: c.plan.resolve_relations(),
396 })
397 .collect();
398 if let Some(inner) = self.source.subquery.take() {
399 self.source.subquery = Some(Box::new(inner.resolve_relations()));
400 }
401 self
402 }
403
404 pub fn has_foreign_origin(&self, origin: PlanOrigin) -> bool {
407 if self.origin.is_some_and(|o| o != origin) {
408 return true;
409 }
410 let exprs = self
411 .projection
412 .iter()
413 .map(|s| &s.expr)
414 .chain(self.filter.iter())
415 .chain(self.having.iter())
416 .chain(self.grouping.iter())
417 .chain(self.ordering.iter().map(|o| &o.expr))
418 .chain(self.joins.iter().map(|j| &j.on));
419 exprs.into_iter().any(|e| e.has_foreign_origin(origin))
420 || self
421 .compound
422 .iter()
423 .any(|c| c.plan.has_foreign_origin(origin))
424 || self
425 .source
426 .subquery
427 .as_deref()
428 .is_some_and(|p| p.has_foreign_origin(origin))
429 || self
430 .joins
431 .iter()
432 .filter_map(|j| j.source.subquery.as_deref())
433 .any(|p| p.has_foreign_origin(origin))
434 }
435
436 pub fn check(&self, caps: &BackendCapabilities) -> Result<(), BackendCapabilityError> {
438 self.required_features()
439 .into_iter()
440 .try_for_each(|f| caps.require(f))
441 }
442}
443
444impl Expr {
445 pub fn has_foreign_origin(&self, origin: PlanOrigin) -> bool {
448 self.subplan().is_some_and(|p| p.has_foreign_origin(origin))
449 || self
450 .children()
451 .into_iter()
452 .any(|c| c.has_foreign_origin(origin))
453 }
454
455 pub fn required_features(&self) -> Vec<Feature> {
457 let mut out = Vec::new();
458 visit(self, &mut |f| {
459 if !out.contains(&f) {
460 out.push(f);
461 }
462 });
463 out
464 }
465}
466
467fn visit(expr: &Expr, need: &mut impl FnMut(Feature)) {
468 match expr {
469 Expr::Lookup {
470 lookup: Lookup::Regex(_),
471 ..
472 } => need(Feature::Regex),
473 Expr::Related(_) => need(Feature::Joins),
474 Expr::Aggregate(agg) => aggregate_features(agg, need),
475 Expr::Window(window) => {
476 need(Feature::WindowFunctions);
477 if let WindowFunc::Aggregate(agg) = &window.func {
478 aggregate_features(agg, need);
479 }
480 }
481 _ => {}
482 }
483 if let Some(plan) = expr.subplan() {
484 need(Feature::Subqueries);
485 plan.required_features().into_iter().for_each(&mut *need);
486 }
487 for child in expr.children() {
488 visit(child, need);
489 }
490}
491
492fn aggregate_features(agg: &Aggregate, need: &mut impl FnMut(Feature)) {
493 match agg.func {
494 AggFunc::StdDev { .. } | AggFunc::Variance { .. } => need(Feature::StatisticalAggregates),
495 AggFunc::ArrayAgg => need(Feature::Arrays),
496 _ => {}
497 }
498}
499
500fn resolve_expr(expr: &mut Expr, root: &Ident, joins: &mut Vec<JoinExpr>) {
501 if let Expr::Related(related) = expr {
502 *expr = Expr::Column(join_path(related, root, joins));
503 return;
504 }
505 if let Some(sub) = expr.subplan_mut() {
506 let taken = std::mem::replace(sub, QueryPlan::from_table(""));
507 *sub = taken.resolve_relations();
508 }
509 for child in expr.children_mut() {
510 resolve_expr(child, root, joins);
511 }
512}
513
514fn join_path(related: &RelatedColumn, root: &Ident, joins: &mut Vec<JoinExpr>) -> Column {
517 let mut parent = root.to_string();
518 for depth in 1..=related.path.len() {
519 let hop = &related.path[depth - 1];
520 let alias = path_alias_of(&related.path[..depth]);
521 if !joins
522 .iter()
523 .any(|j| j.source.alias.as_deref() == Some(alias.as_str()))
524 {
525 joins.push(JoinExpr {
526 kind: JoinKind::Left,
527 source: QuerySource {
528 name: hop.table.clone(),
529 alias: Some(alias.clone().into()),
530 subquery: None,
531 },
532 on: Expr::Column(Column::qualified(parent.clone(), hop.fk_column.clone())).eq(
533 Expr::Column(Column::qualified(alias.clone(), hop.pk_column.clone())),
534 ),
535 });
536 }
537 parent = alias;
538 }
539 Column::qualified(parent, related.column.clone())
540}
541
542#[cfg(test)]
543mod tests {
544 use super::*;
545 use crate::capabilities::BackendKind;
546
547 #[test]
548 fn filters_accumulate_with_and() {
549 let p = QueryPlan::from_table("users")
550 .filter(Expr::col("a").eq(1))
551 .filter(Expr::col("b").eq(2));
552 assert!(matches!(p.filter, Some(Expr::And(ref v)) if v.len() == 2));
553 }
554
555 #[test]
556 fn branching_is_independent() {
557 let base = QueryPlan::from_table("users").filter(Expr::col("active").eq(true));
558 let a = base.clone().limit(10);
559 let b = base.clone().order_by(Expr::col("id"), OrderDirection::Desc);
560 assert_eq!(base.limit, None);
561 assert_eq!(a.limit, Some(10));
562 assert!(b.limit.is_none() && b.ordering.len() == 1);
563 }
564
565 #[test]
566 fn capability_check_detects_nested_regex_and_locks() {
567 let sub = QueryPlan::from_table("t").filter(Expr::col("x").lookup_regex("^a"));
568 let p = QueryPlan::from_table("users")
569 .filter(Expr::Exists(Box::new(sub)))
570 .lock(LockMode::ForUpdateSkipLocked);
571 let feats = p.required_features();
572 assert!(feats.contains(&Feature::Regex));
573 assert!(feats.contains(&Feature::LockModifiers));
574 let err = p.check(&BackendCapabilities::sqlite());
575 assert_eq!(
576 err,
577 Err(BackendCapabilityError::RowLockingUnsupported {
578 backend: BackendKind::Sqlite
579 })
580 );
581 assert!(p.check(&BackendCapabilities::postgres()).is_ok());
582 }
583
584 trait RegexExt {
585 fn lookup_regex(self, p: &str) -> Expr;
586 }
587 impl RegexExt for Expr {
588 fn lookup_regex(self, p: &str) -> Expr {
589 Expr::Lookup {
590 expr: Box::new(self),
591 lookup: Lookup::Regex(p.into()),
592 }
593 }
594 }
595}