1use crate::iden::{ColumnRef, Ident, IntoIden};
19use crate::query::Select;
20use crate::value::Value;
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub enum Order {
25 Asc,
27 Desc,
29}
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33#[non_exhaustive]
34pub enum BinOp {
35 Eq,
37 Ne,
39 Lt,
41 Lte,
43 Gt,
45 Gte,
47 And,
49 Or,
51 Is,
53 IsNot,
55 Add,
57 Sub,
59 Mul,
61 Div,
63 Mod,
65 Concat,
67 JsonArrow,
69 JsonArrowText,
71 Match,
73}
74
75impl BinOp {
76 pub(crate) fn sql(self) -> &'static str {
78 match self {
79 BinOp::Eq => "=",
80 BinOp::Ne => "<>",
81 BinOp::Lt => "<",
82 BinOp::Lte => "<=",
83 BinOp::Gt => ">",
84 BinOp::Gte => ">=",
85 BinOp::And => "AND",
86 BinOp::Or => "OR",
87 BinOp::Is => "IS",
88 BinOp::IsNot => "IS NOT",
89 BinOp::Add => "+",
90 BinOp::Sub => "-",
91 BinOp::Mul => "*",
92 BinOp::Div => "/",
93 BinOp::Mod => "%",
94 BinOp::Concat => "||",
95 BinOp::JsonArrow => "->",
96 BinOp::JsonArrowText => "->>",
97 BinOp::Match => "MATCH",
98 }
99 }
100}
101
102#[derive(Clone, Debug, PartialEq)]
108#[non_exhaustive]
109#[must_use = "an expression does nothing until used in a statement"]
110pub enum Expr {
111 Column(ColumnRef),
113 Value(Value),
115 Tuple(Vec<Expr>),
118 Binary(Box<Expr>, BinOp, Box<Expr>),
120 Not(Box<Expr>),
122 Neg(Box<Expr>),
124 IsNull(Box<Expr>),
126 IsNotNull(Box<Expr>),
128 In(Box<Expr>, Box<Expr>),
130 NotIn(Box<Expr>, Box<Expr>),
132 Between(Box<Expr>, Box<Expr>, Box<Expr>),
134 NotBetween(Box<Expr>, Box<Expr>, Box<Expr>),
136 Like {
143 expr: Box<Expr>,
145 pattern: Box<Expr>,
147 negated: bool,
149 escape: Option<char>,
151 },
152 Func(Func),
154 Subquery(Box<Select>),
156 Exists(Box<Select>),
158 Case(Vec<(Expr, Expr)>, Option<Box<Expr>>),
160 Cast(Box<Expr>, &'static str),
162 Alias(Box<Expr>, Ident),
165 Raw(String, Vec<Value>),
168 Paren(Box<Expr>),
170}
171
172#[derive(Clone, Debug, PartialEq)]
174pub struct Func {
175 pub name: &'static str,
177 pub args: Vec<Expr>,
179 pub distinct: bool,
181}
182
183impl Func {
184 pub fn call(name: &'static str, args: impl IntoIterator<Item = Expr>) -> Expr {
189 Expr::Func(Func {
190 name,
191 args: args.into_iter().collect(),
192 distinct: false,
193 })
194 }
195
196 pub fn count(expr: Expr) -> Expr {
198 Self::call("COUNT", [expr])
199 }
200
201 pub fn count_star() -> Expr {
203 Self::call("COUNT", [Expr::Column(ColumnRef::Asterisk)])
204 }
205
206 pub fn count_distinct(expr: Expr) -> Expr {
208 Expr::Func(Func {
209 name: "COUNT",
210 args: vec![expr],
211 distinct: true,
212 })
213 }
214
215 pub fn max(expr: Expr) -> Expr {
217 Self::call("MAX", [expr])
218 }
219
220 pub fn min(expr: Expr) -> Expr {
222 Self::call("MIN", [expr])
223 }
224
225 pub fn sum(expr: Expr) -> Expr {
227 Self::call("SUM", [expr])
228 }
229
230 pub fn avg(expr: Expr) -> Expr {
232 Self::call("AVG", [expr])
233 }
234
235 pub fn coalesce(args: impl IntoIterator<Item = Expr>) -> Expr {
237 Self::call("COALESCE", args)
238 }
239
240 pub fn lower(expr: Expr) -> Expr {
242 Self::call("LOWER", [expr])
243 }
244
245 pub fn upper(expr: Expr) -> Expr {
247 Self::call("UPPER", [expr])
248 }
249
250 pub fn length(expr: Expr) -> Expr {
252 Self::call("LENGTH", [expr])
253 }
254
255 pub fn abs(expr: Expr) -> Expr {
257 Self::call("ABS", [expr])
258 }
259
260 pub fn if_null(a: Expr, b: Expr) -> Expr {
262 Self::call("IFNULL", [a, b])
263 }
264
265 pub fn json_extract(json: Expr, path: impl Into<Value>) -> Expr {
267 Self::call("json_extract", [json, Expr::Value(path.into())])
268 }
269
270 pub fn vector_distance_cos(a: Expr, b: Expr) -> Expr {
272 Self::call("vector_distance_cos", [a, b])
273 }
274
275 pub fn vector_distance_l2(a: Expr, b: Expr) -> Expr {
277 Self::call("vector_distance_l2", [a, b])
278 }
279
280 pub fn vector32(expr: Expr) -> Expr {
282 Self::call("vector32", [expr])
283 }
284
285 pub fn fts_match(column: Expr, query: impl Into<Value>) -> Expr {
288 Self::call("fts_match", [column, Expr::Value(query.into())])
289 }
290
291 pub fn fts_score(column: Expr) -> Expr {
293 Self::call("fts_score", [column])
294 }
295}
296
297fn bin(lhs: Expr, op: BinOp, rhs: Expr) -> Expr {
299 Expr::Binary(Box::new(lhs), op, Box::new(rhs))
300}
301
302impl Expr {
303 pub fn col(column: impl Into<ColumnRef>) -> Self {
305 Expr::Column(column.into())
306 }
307
308 pub fn val(value: impl Into<Value>) -> Self {
310 Expr::Value(value.into())
311 }
312
313 pub fn tuple<V: Into<Value>>(values: impl IntoIterator<Item = V>) -> Self {
315 Expr::Tuple(values.into_iter().map(|v| Expr::Value(v.into())).collect())
316 }
317
318 pub fn subquery(select: Select) -> Self {
320 Expr::Subquery(Box::new(select))
321 }
322
323 pub fn exists(select: Select) -> Self {
325 Expr::Exists(Box::new(select))
326 }
327
328 pub fn cust(sql: impl Into<String>) -> Self {
330 Expr::Raw(sql.into(), Vec::new())
331 }
332
333 pub fn cust_with_values<V: Into<Value>>(
336 sql: impl Into<String>,
337 values: impl IntoIterator<Item = V>,
338 ) -> Self {
339 Expr::Raw(sql.into(), values.into_iter().map(Into::into).collect())
340 }
341
342 pub fn eq(self, rhs: impl Into<Expr>) -> Self {
344 bin(self, BinOp::Eq, rhs.into())
345 }
346
347 pub fn ne(self, rhs: impl Into<Expr>) -> Self {
349 bin(self, BinOp::Ne, rhs.into())
350 }
351
352 pub fn lt(self, rhs: impl Into<Expr>) -> Self {
354 bin(self, BinOp::Lt, rhs.into())
355 }
356
357 pub fn lte(self, rhs: impl Into<Expr>) -> Self {
359 bin(self, BinOp::Lte, rhs.into())
360 }
361
362 pub fn gt(self, rhs: impl Into<Expr>) -> Self {
364 bin(self, BinOp::Gt, rhs.into())
365 }
366
367 pub fn gte(self, rhs: impl Into<Expr>) -> Self {
369 bin(self, BinOp::Gte, rhs.into())
370 }
371
372 pub fn and(self, rhs: impl Into<Expr>) -> Self {
374 bin(self, BinOp::And, rhs.into())
375 }
376
377 pub fn or(self, rhs: impl Into<Expr>) -> Self {
379 bin(self, BinOp::Or, rhs.into())
380 }
381
382 #[allow(
384 clippy::should_implement_trait,
385 reason = "the SQL-flavoured name reads as the operator it builds"
386 )]
387 pub fn not(self) -> Self {
388 Expr::Not(Box::new(self))
389 }
390
391 #[allow(
393 clippy::should_implement_trait,
394 reason = "the SQL-flavoured name reads as the operator it builds"
395 )]
396 pub fn neg(self) -> Self {
397 Expr::Neg(Box::new(self))
398 }
399
400 pub fn is_null(self) -> Self {
402 Expr::IsNull(Box::new(self))
403 }
404
405 pub fn is_not_null(self) -> Self {
407 Expr::IsNotNull(Box::new(self))
408 }
409
410 pub fn is(self, rhs: impl Into<Expr>) -> Self {
412 bin(self, BinOp::Is, rhs.into())
413 }
414
415 pub fn is_not(self, rhs: impl Into<Expr>) -> Self {
417 bin(self, BinOp::IsNot, rhs.into())
418 }
419
420 pub fn like(self, pattern: impl Into<Value>) -> Self {
426 Expr::Like {
427 expr: Box::new(self),
428 pattern: Box::new(Expr::Value(pattern.into())),
429 negated: false,
430 escape: None,
431 }
432 }
433
434 pub fn not_like(self, pattern: impl Into<Value>) -> Self {
436 Expr::Like {
437 expr: Box::new(self),
438 pattern: Box::new(Expr::Value(pattern.into())),
439 negated: true,
440 escape: None,
441 }
442 }
443
444 pub fn like_escaped(self, pattern: impl Into<Value>, escape: char) -> Self {
447 Expr::Like {
448 expr: Box::new(self),
449 pattern: Box::new(Expr::Value(pattern.into())),
450 negated: false,
451 escape: Some(escape),
452 }
453 }
454
455 pub fn matches(self, query: impl Into<Value>) -> Self {
458 bin(self, BinOp::Match, Expr::Value(query.into()))
459 }
460
461 pub fn is_in<V: Into<Value>>(self, values: impl IntoIterator<Item = V>) -> Self {
467 Expr::In(Box::new(self), Box::new(Expr::tuple(values)))
468 }
469
470 pub fn is_not_in<V: Into<Value>>(self, values: impl IntoIterator<Item = V>) -> Self {
472 Expr::NotIn(Box::new(self), Box::new(Expr::tuple(values)))
473 }
474
475 pub fn in_subquery(self, select: Select) -> Self {
477 Expr::In(Box::new(self), Box::new(Expr::subquery(select)))
478 }
479
480 pub fn not_in_subquery(self, select: Select) -> Self {
482 Expr::NotIn(Box::new(self), Box::new(Expr::subquery(select)))
483 }
484
485 pub fn between(self, a: impl Into<Expr>, b: impl Into<Expr>) -> Self {
487 Expr::Between(Box::new(self), Box::new(a.into()), Box::new(b.into()))
488 }
489
490 pub fn not_between(self, a: impl Into<Expr>, b: impl Into<Expr>) -> Self {
492 Expr::NotBetween(Box::new(self), Box::new(a.into()), Box::new(b.into()))
493 }
494
495 #[allow(
497 clippy::should_implement_trait,
498 reason = "the SQL-flavoured name reads as the operator it builds"
499 )]
500 pub fn add(self, rhs: impl Into<Expr>) -> Self {
501 bin(self, BinOp::Add, rhs.into())
502 }
503
504 #[allow(
506 clippy::should_implement_trait,
507 reason = "the SQL-flavoured name reads as the operator it builds"
508 )]
509 pub fn sub(self, rhs: impl Into<Expr>) -> Self {
510 bin(self, BinOp::Sub, rhs.into())
511 }
512
513 #[allow(
515 clippy::should_implement_trait,
516 reason = "the SQL-flavoured name reads as the operator it builds"
517 )]
518 pub fn mul(self, rhs: impl Into<Expr>) -> Self {
519 bin(self, BinOp::Mul, rhs.into())
520 }
521
522 #[allow(
524 clippy::should_implement_trait,
525 reason = "the SQL-flavoured name reads as the operator it builds"
526 )]
527 pub fn div(self, rhs: impl Into<Expr>) -> Self {
528 bin(self, BinOp::Div, rhs.into())
529 }
530
531 #[allow(
533 clippy::should_implement_trait,
534 reason = "the SQL-flavoured name reads as the operator it builds"
535 )]
536 pub fn rem(self, rhs: impl Into<Expr>) -> Self {
537 bin(self, BinOp::Mod, rhs.into())
538 }
539
540 pub fn concat(self, rhs: impl Into<Expr>) -> Self {
542 bin(self, BinOp::Concat, rhs.into())
543 }
544
545 pub fn json_get(self, path: impl Into<Value>) -> Self {
548 bin(self, BinOp::JsonArrow, Expr::Value(path.into()))
549 }
550
551 pub fn json_get_text(self, path: impl Into<Value>) -> Self {
553 bin(self, BinOp::JsonArrowText, Expr::Value(path.into()))
554 }
555
556 pub fn cast_as(self, ty: &'static str) -> Self {
558 Expr::Cast(Box::new(self), ty)
559 }
560
561 pub fn alias(self, alias: impl IntoIden) -> Self {
563 Expr::Alias(Box::new(self), alias.into_iden())
564 }
565
566 pub fn paren(self) -> Self {
568 Expr::Paren(Box::new(self))
569 }
570
571 pub fn case(whens: Vec<(Expr, Expr)>, otherwise: Option<Expr>) -> Self {
573 Expr::Case(whens, otherwise.map(Box::new))
574 }
575
576 pub fn contains(self, s: &str) -> Self {
579 self.like_escaped(format!("%{}%", escape_like(s)), LIKE_ESCAPE)
580 }
581
582 pub fn starts_with(self, s: &str) -> Self {
585 self.like_escaped(format!("{}%", escape_like(s)), LIKE_ESCAPE)
586 }
587
588 pub fn ends_with(self, s: &str) -> Self {
591 self.like_escaped(format!("%{}", escape_like(s)), LIKE_ESCAPE)
592 }
593}
594
595const LIKE_ESCAPE: char = '\\';
598
599fn escape_like(s: &str) -> String {
602 let mut out = String::with_capacity(s.len());
603 for c in s.chars() {
604 if c == '%' || c == '_' || c == LIKE_ESCAPE {
605 out.push(LIKE_ESCAPE);
606 }
607 out.push(c);
608 }
609 out
610}
611
612impl<T: Into<Value>> From<T> for Expr {
613 fn from(value: T) -> Self {
614 Expr::Value(value.into())
615 }
616}
617
618#[derive(Clone, Debug, PartialEq)]
635pub struct Condition {
636 all: bool,
638 negate: bool,
640 parts: Vec<Expr>,
642}
643
644impl Condition {
645 pub fn all() -> Self {
649 Self {
650 all: true,
651 negate: false,
652 parts: Vec::new(),
653 }
654 }
655
656 pub fn any() -> Self {
662 Self {
663 all: false,
664 negate: false,
665 parts: Vec::new(),
666 }
667 }
668
669 #[must_use]
674 #[allow(
675 clippy::should_implement_trait,
676 reason = "the name reads as the SQL it builds"
677 )]
678 pub fn add(mut self, part: impl IntoCondition) -> Self {
679 if let Some(expr) = part.into_condition().into_expr() {
680 self.parts.push(expr);
681 }
682 self
683 }
684
685 #[must_use]
687 pub fn add_option(self, part: Option<impl IntoCondition>) -> Self {
688 match part {
689 Some(p) => self.add(p),
690 None => self,
691 }
692 }
693
694 #[must_use]
696 #[allow(
697 clippy::should_implement_trait,
698 reason = "the name reads as the SQL it builds"
699 )]
700 pub fn not(mut self) -> Self {
701 self.negate = !self.negate;
702 self
703 }
704
705 pub fn is_empty(&self) -> bool {
707 self.parts.is_empty()
708 }
709
710 pub fn len(&self) -> usize {
712 self.parts.len()
713 }
714
715 pub fn into_expr(self) -> Option<Expr> {
717 let op = if self.all { BinOp::And } else { BinOp::Or };
718 let needs_paren = !self.all || self.negate;
721 let negate = self.negate;
722 let mut iter = self.parts.into_iter();
723 let first = iter.next()?;
724 let joined = iter.fold(first, |acc, e| bin(acc, op, e));
725 let joined = if needs_paren {
726 Expr::Paren(Box::new(joined))
727 } else {
728 joined
729 };
730 Some(if negate { joined.not() } else { joined })
731 }
732}
733
734impl Default for Condition {
735 fn default() -> Self {
736 Condition::all()
737 }
738}
739
740pub trait IntoCondition {
742 fn into_condition(self) -> Condition;
744}
745
746impl IntoCondition for Condition {
747 fn into_condition(self) -> Condition {
748 self
749 }
750}
751
752impl IntoCondition for Expr {
753 fn into_condition(self) -> Condition {
754 Condition::all().add_expr(self)
755 }
756}
757
758impl Condition {
759 fn add_expr(mut self, expr: Expr) -> Self {
762 self.parts.push(expr);
763 self
764 }
765}