1mod aggregate;
18mod field;
19pub mod functions;
20mod related;
21mod window;
22
23pub use aggregate::{
24 AggFunc, Aggregate, ArrayAgg, Avg, Count, Max, Min, StdDev, StringAgg, Sum, Variance,
25};
26pub use field::{Calc, DateLike, Field, Literal, Numeric, Operand, TextLike, TimeLike};
27pub use functions::{CaseBuilder, DatePart, Function};
28pub use related::{FkSlot, Joined, RelHop, Related, RelatedColumn, path_alias_of};
29pub use window::{
30 CumeDist, DenseRank, FirstValue, Lag, LastValue, Lead, Ntile, PercentRank, Rank, RowNumber,
31 Window, WindowFunc,
32};
33
34use crate::model::Model;
35use crate::plan::{OrderDirection, OrderExpr, QueryPlan};
36use crate::types::SqlType;
37use crate::value::Value;
38use std::borrow::Cow;
39
40pub type Ident = Cow<'static, str>;
42
43#[derive(Debug, Clone, PartialEq)]
45pub struct Column {
46 pub source: Option<Ident>,
48 pub name: Ident,
50}
51
52impl Column {
53 pub fn new(name: impl Into<Ident>) -> Self {
55 Self {
56 source: None,
57 name: name.into(),
58 }
59 }
60
61 pub fn qualified(source: impl Into<Ident>, name: impl Into<Ident>) -> Self {
63 Self {
64 source: Some(source.into()),
65 name: name.into(),
66 }
67 }
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72#[allow(missing_docs)]
73pub enum BinaryOp {
74 Eq,
75 Ne,
76 Lt,
77 Le,
78 Gt,
79 Ge,
80 Add,
81 Sub,
82 Mul,
83 Div,
84 Mod,
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
89pub enum UnaryOp {
90 Not,
92 Neg,
94}
95
96#[derive(Debug, Clone, PartialEq)]
98pub enum Lookup {
99 IExact(Value),
101 Contains {
103 needle: String,
105 case_insensitive: bool,
107 },
108 StartsWith {
110 needle: String,
112 case_insensitive: bool,
114 },
115 EndsWith {
117 needle: String,
119 case_insensitive: bool,
121 },
122 Regex(String),
124 In(Vec<Value>),
126 InSubquery(Box<QueryPlan>),
128 Range(Value, Value),
130 IsNull(bool),
132}
133
134#[derive(Debug, Clone, PartialEq)]
136pub enum Expr {
137 Column(Column),
139 Value(Value),
141 Binary {
143 op: BinaryOp,
145 lhs: Box<Expr>,
147 rhs: Box<Expr>,
149 },
150 Unary {
152 op: UnaryOp,
154 expr: Box<Expr>,
156 },
157 And(Vec<Expr>),
159 Or(Vec<Expr>),
161 Lookup {
163 expr: Box<Expr>,
165 lookup: Lookup,
167 },
168 Exists(Box<QueryPlan>),
170 Subquery(Box<QueryPlan>),
172 Func {
174 func: Function,
176 args: Vec<Expr>,
178 },
179 Cast {
181 expr: Box<Expr>,
183 ty: SqlType,
185 },
186 Case {
188 branches: Vec<(Expr, Expr)>,
190 otherwise: Option<Box<Expr>>,
192 },
193 Aggregate(Aggregate),
195 Window(Box<Window>),
197 DatePart {
199 part: DatePart,
201 expr: Box<Expr>,
203 },
204 OuterRef(Column),
206 Related(RelatedColumn),
209}
210
211impl Expr {
212 pub fn col(name: impl Into<Ident>) -> Self {
214 Expr::Column(Column::new(name))
215 }
216
217 pub fn val(v: impl Into<Value>) -> Self {
219 Expr::Value(v.into())
220 }
221
222 pub fn outer(name: impl Into<Ident>) -> Self {
224 Expr::OuterRef(Column::new(name))
225 }
226
227 pub fn exists(plan: QueryPlan) -> Self {
229 Expr::Exists(Box::new(plan))
230 }
231
232 pub fn subquery(plan: QueryPlan) -> Self {
234 Expr::Subquery(Box::new(plan))
235 }
236
237 fn binary(self, op: BinaryOp, rhs: impl Into<Expr>) -> Self {
238 Expr::Binary {
239 op,
240 lhs: Box::new(self),
241 rhs: Box::new(rhs.into()),
242 }
243 }
244
245 fn lookup(self, lookup: Lookup) -> Self {
246 Expr::Lookup {
247 expr: Box::new(self),
248 lookup,
249 }
250 }
251
252 pub fn eq(self, rhs: impl Into<Expr>) -> Self {
254 self.binary(BinaryOp::Eq, rhs)
255 }
256 pub fn ne(self, rhs: impl Into<Expr>) -> Self {
258 self.binary(BinaryOp::Ne, rhs)
259 }
260 pub fn lt(self, rhs: impl Into<Expr>) -> Self {
262 self.binary(BinaryOp::Lt, rhs)
263 }
264 pub fn le(self, rhs: impl Into<Expr>) -> Self {
266 self.binary(BinaryOp::Le, rhs)
267 }
268 pub fn gt(self, rhs: impl Into<Expr>) -> Self {
270 self.binary(BinaryOp::Gt, rhs)
271 }
272 pub fn ge(self, rhs: impl Into<Expr>) -> Self {
274 self.binary(BinaryOp::Ge, rhs)
275 }
276
277 pub fn and(self, rhs: Expr) -> Self {
279 match (self, rhs) {
280 (Expr::And(mut a), Expr::And(b)) => {
281 a.extend(b);
282 Expr::And(a)
283 }
284 (Expr::And(mut a), b) => {
285 a.push(b);
286 Expr::And(a)
287 }
288 (a, b) => Expr::And(vec![a, b]),
289 }
290 }
291
292 pub fn or(self, rhs: Expr) -> Self {
294 match (self, rhs) {
295 (Expr::Or(mut a), Expr::Or(b)) => {
296 a.extend(b);
297 Expr::Or(a)
298 }
299 (Expr::Or(mut a), b) => {
300 a.push(b);
301 Expr::Or(a)
302 }
303 (a, b) => Expr::Or(vec![a, b]),
304 }
305 }
306
307 pub fn is_in<I, V>(self, values: I) -> Self
309 where
310 I: IntoIterator<Item = V>,
311 V: Into<Value>,
312 {
313 self.lookup(Lookup::In(values.into_iter().map(Into::into).collect()))
314 }
315
316 pub fn in_subquery(self, plan: QueryPlan) -> Self {
318 self.lookup(Lookup::InSubquery(Box::new(plan)))
319 }
320
321 pub fn range(self, lo: impl Into<Value>, hi: impl Into<Value>) -> Self {
323 self.lookup(Lookup::Range(lo.into(), hi.into()))
324 }
325
326 pub fn is_null(self, yes: bool) -> Self {
328 self.lookup(Lookup::IsNull(yes))
329 }
330
331 pub fn iexact(self, v: impl Into<Value>) -> Self {
333 self.lookup(Lookup::IExact(v.into()))
334 }
335
336 pub fn regex(self, pattern: impl Into<String>) -> Self {
338 self.lookup(Lookup::Regex(pattern.into()))
339 }
340
341 pub fn cast(self, ty: SqlType) -> Self {
343 Expr::Cast {
344 expr: Box::new(self),
345 ty,
346 }
347 }
348
349 pub fn date_part(self, part: DatePart) -> Self {
351 Expr::DatePart {
352 part,
353 expr: Box::new(self),
354 }
355 }
356
357 pub fn asc(self) -> OrderExpr {
359 OrderExpr {
360 expr: self,
361 direction: OrderDirection::Asc,
362 }
363 }
364
365 pub fn desc(self) -> OrderExpr {
367 OrderExpr {
368 expr: self,
369 direction: OrderDirection::Desc,
370 }
371 }
372
373 pub fn children(&self) -> Vec<&Expr> {
376 match self {
377 Expr::Column(_)
378 | Expr::Value(_)
379 | Expr::OuterRef(_)
380 | Expr::Related(_)
381 | Expr::Exists(_)
382 | Expr::Subquery(_) => Vec::new(),
383 Expr::Binary { lhs, rhs, .. } => vec![lhs, rhs],
384 Expr::Unary { expr, .. }
385 | Expr::Lookup { expr, .. }
386 | Expr::Cast { expr, .. }
387 | Expr::DatePart { expr, .. } => vec![expr],
388 Expr::And(items) | Expr::Or(items) => items.iter().collect(),
389 Expr::Func { args, .. } => args.iter().collect(),
390 Expr::Case {
391 branches,
392 otherwise,
393 } => branches
394 .iter()
395 .flat_map(|(when, then)| [when, then])
396 .chain(otherwise.as_deref())
397 .collect(),
398 Expr::Aggregate(agg) => agg.children(),
399 Expr::Window(window) => window.children(),
400 }
401 }
402
403 pub fn children_mut(&mut self) -> Vec<&mut Expr> {
405 match self {
406 Expr::Column(_)
407 | Expr::Value(_)
408 | Expr::OuterRef(_)
409 | Expr::Related(_)
410 | Expr::Exists(_)
411 | Expr::Subquery(_) => Vec::new(),
412 Expr::Binary { lhs, rhs, .. } => vec![lhs, rhs],
413 Expr::Unary { expr, .. }
414 | Expr::Lookup { expr, .. }
415 | Expr::Cast { expr, .. }
416 | Expr::DatePart { expr, .. } => vec![expr],
417 Expr::And(items) | Expr::Or(items) => items.iter_mut().collect(),
418 Expr::Func { args, .. } => args.iter_mut().collect(),
419 Expr::Case {
420 branches,
421 otherwise,
422 } => branches
423 .iter_mut()
424 .flat_map(|(when, then)| [when, then])
425 .chain(otherwise.as_deref_mut())
426 .collect(),
427 Expr::Aggregate(agg) => agg.children_mut(),
428 Expr::Window(window) => window.children_mut(),
429 }
430 }
431
432 pub fn subplan(&self) -> Option<&QueryPlan> {
434 match self {
435 Expr::Exists(plan)
436 | Expr::Subquery(plan)
437 | Expr::Lookup {
438 lookup: Lookup::InSubquery(plan),
439 ..
440 } => Some(plan),
441 _ => None,
442 }
443 }
444
445 pub fn subplan_mut(&mut self) -> Option<&mut QueryPlan> {
447 match self {
448 Expr::Exists(plan)
449 | Expr::Subquery(plan)
450 | Expr::Lookup {
451 lookup: Lookup::InSubquery(plan),
452 ..
453 } => Some(plan),
454 _ => None,
455 }
456 }
457
458 pub fn contains_aggregate(&self) -> bool {
462 matches!(self, Expr::Aggregate(_)) || self.children().iter().any(|c| c.contains_aggregate())
463 }
464
465 pub fn contains_window(&self) -> bool {
467 matches!(self, Expr::Window(_)) || self.children().iter().any(|c| c.contains_window())
468 }
469}
470
471impl std::ops::Not for Expr {
472 type Output = Expr;
473 fn not(self) -> Expr {
474 Expr::Unary {
475 op: UnaryOp::Not,
476 expr: Box::new(self),
477 }
478 }
479}
480
481impl std::ops::Neg for Expr {
482 type Output = Expr;
483 fn neg(self) -> Expr {
484 Expr::Unary {
485 op: UnaryOp::Neg,
486 expr: Box::new(self),
487 }
488 }
489}
490
491macro_rules! arith_ops {
492 ($($trait:ident $method:ident $op:ident),*) => {$(
493 impl<R: Into<Expr>> std::ops::$trait<R> for Expr {
494 type Output = Expr;
495 fn $method(self, rhs: R) -> Expr { self.binary(BinaryOp::$op, rhs) }
496 }
497 )*};
498}
499arith_ops!(Add add Add, Sub sub Sub, Mul mul Mul, Div div Div, Rem rem Mod);
500
501impl<T: Into<Value>> From<T> for Expr {
502 fn from(v: T) -> Self {
503 Expr::Value(v.into())
504 }
505}
506
507pub(crate) fn pk_column<M: Model>() -> Ident {
510 M::META.pk().map_or("id", |f| f.column).into()
511}
512
513#[cfg(test)]
514mod tests {
515 use super::*;
516
517 struct User;
518 #[allow(non_upper_case_globals)]
519 impl User {
520 const name: Field<User, String> = Field::new("name");
521 const likes: Field<User, i64> = Field::new("likes");
522 const dislikes: Field<User, i64> = Field::new("dislikes");
523 const age: Field<User, Option<i32>> = Field::new("age");
524 }
525
526 #[test]
527 fn typed_comparisons_build_trees() {
528 let e = User::likes.gt(User::dislikes);
529 assert_eq!(e, Expr::col("likes").gt(Expr::col("dislikes")));
530 assert_eq!(User::likes.eq(3_i64), Expr::col("likes").eq(3_i64));
531 assert_eq!(User::name.eq("bob"), Expr::col("name").eq("bob"));
532 assert_eq!(User::age.eq(3), Expr::col("age").eq(3));
533 }
534
535 #[test]
536 fn boolean_composition_flattens() {
537 let e = User::name
538 .icontains("al")
539 .or(User::name.ends_with("x"))
540 .or(User::likes.is_null());
541 assert!(matches!(e, Expr::Or(ref v) if v.len() == 3));
542 assert!(matches!(
543 !e,
544 Expr::Unary {
545 op: UnaryOp::Not,
546 ..
547 }
548 ));
549 }
550
551 #[test]
552 fn arithmetic_is_typed_and_composable() {
553 let net = User::likes - User::dislikes;
554 let e = User::likes.eq(net.clone() * 2_i64);
555 assert!(matches!(
556 e,
557 Expr::Binary {
558 op: BinaryOp::Eq,
559 ..
560 }
561 ));
562 assert!(net.gt(0_i64).children().len() == 2);
563 }
564
565 #[test]
566 fn aggregates_are_found_outside_windows_and_subqueries() {
567 let sum = Sum::of(Expr::col("x"));
568 assert!(Expr::from(sum.clone()).gt(1_i64).contains_aggregate());
569 let windowed = Window::over(WindowFunc::Aggregate(sum));
570 assert!(!Expr::from(windowed).contains_aggregate());
571 }
572}