Skip to main content

turso_sql/
expr.rs

1//! SQL expressions and conditions, modeled by [`Expr`] and [`Condition`].
2//!
3//! An [`Expr`] is a plain tree: the builder methods on it only construct
4//! nodes and never render anything, so an expression can be inspected,
5//! cloned and reused across statements. Rendering, including the decision of
6//! where parentheses go, belongs to the writer. The module owns the shape of
7//! the tree and the convenience constructors on it; it does not own the list
8//! of SQL functions Turso supports — [`Func::call`] accepts any name so new
9//! engine functions never require a release of this crate.
10//!
11//! A [`Condition`] is the `WHERE` / `HAVING` building block: an `AND` or
12//! `OR` group that flattens into one [`Expr`] on demand. `OR` groups and
13//! negations always parenthesise themselves when flattened so that mixing
14//! `AND` and `OR` never depends on the reader knowing SQL precedence rules.
15//! An empty condition renders as nothing, which is what lets builders start
16//! from `Condition::all()` and add filters incrementally.
17
18use crate::iden::{ColumnRef, Ident, IntoIden};
19use crate::query::Select;
20use crate::value::Value;
21
22/// A sort direction.
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub enum Order {
25    /// Ascending (`ASC`).
26    Asc,
27    /// Descending (`DESC`).
28    Desc,
29}
30
31/// The binary operators an [`Expr::Binary`] node can carry.
32#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33#[non_exhaustive]
34pub enum BinOp {
35    /// The `=` operator.
36    Eq,
37    /// The `<>` operator.
38    Ne,
39    /// The `<` operator.
40    Lt,
41    /// The `<=` operator.
42    Lte,
43    /// The `>` operator.
44    Gt,
45    /// The `>=` operator.
46    Gte,
47    /// The `AND` operator.
48    And,
49    /// The `OR` operator.
50    Or,
51    /// The `IS` operator.
52    Is,
53    /// The `IS NOT` operator.
54    IsNot,
55    /// The `+` operator.
56    Add,
57    /// The `-` operator.
58    Sub,
59    /// The `*` operator.
60    Mul,
61    /// The `/` operator.
62    Div,
63    /// The `%` operator.
64    Mod,
65    /// The `||` string concatenation operator.
66    Concat,
67    /// The `->` JSON extraction operator.
68    JsonArrow,
69    /// The `->>` JSON extraction operator, yielding a SQL value.
70    JsonArrowText,
71    /// The `MATCH` full-text search operator.
72    Match,
73}
74
75impl BinOp {
76    /// The operator's SQL spelling.
77    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/// A SQL expression.
103///
104/// The enum is `#[non_exhaustive]` so that new node kinds can be added
105/// without breaking downstream matches; construct nodes through the
106/// associated functions and methods rather than the variants directly.
107#[derive(Clone, Debug, PartialEq)]
108#[non_exhaustive]
109#[must_use = "an expression does nothing until used in a statement"]
110pub enum Expr {
111    /// A column reference.
112    Column(ColumnRef),
113    /// A bound value.
114    Value(Value),
115    /// A parenthesised list of expressions, for example the right-hand side
116    /// of `IN`.
117    Tuple(Vec<Expr>),
118    /// A binary operation `lhs op rhs`.
119    Binary(Box<Expr>, BinOp, Box<Expr>),
120    /// A logical negation `NOT expr`.
121    Not(Box<Expr>),
122    /// An arithmetic negation `-expr`.
123    Neg(Box<Expr>),
124    /// The `expr IS NULL` test.
125    IsNull(Box<Expr>),
126    /// The `expr IS NOT NULL` test.
127    IsNotNull(Box<Expr>),
128    /// The `expr IN (...)` membership test.
129    In(Box<Expr>, Box<Expr>),
130    /// The `expr NOT IN (...)` membership test.
131    NotIn(Box<Expr>, Box<Expr>),
132    /// The `expr BETWEEN a AND b` range test.
133    Between(Box<Expr>, Box<Expr>, Box<Expr>),
134    /// The `expr NOT BETWEEN a AND b` range test.
135    NotBetween(Box<Expr>, Box<Expr>, Box<Expr>),
136    /// A pattern test `expr [NOT] LIKE pattern [ESCAPE 'c']`.
137    ///
138    /// The escape character is carried on the node because SQLite treats a
139    /// backslash in a pattern literally unless the statement says
140    /// otherwise; the `contains` family sets it so that `%` and `_` in
141    /// user input match themselves.
142    Like {
143        /// The tested expression.
144        expr: Box<Expr>,
145        /// The pattern, normally a bound value.
146        pattern: Box<Expr>,
147        /// Whether the test is `NOT LIKE`.
148        negated: bool,
149        /// The `ESCAPE` character, when the pattern uses one.
150        escape: Option<char>,
151    },
152    /// A function call `name(args)`.
153    Func(Func),
154    /// A scalar subquery `(SELECT ...)`.
155    Subquery(Box<Select>),
156    /// An existence test `EXISTS (SELECT ...)`.
157    Exists(Box<Select>),
158    /// A `CASE WHEN ... THEN ... ELSE ... END` expression.
159    Case(Vec<(Expr, Expr)>, Option<Box<Expr>>),
160    /// A type conversion `CAST(expr AS type)`.
161    Cast(Box<Expr>, &'static str),
162    /// An aliased expression `expr AS alias`, only meaningful in select
163    /// lists.
164    Alias(Box<Expr>, Ident),
165    /// Raw SQL inserted verbatim, with `?` placeholders for the values
166    /// carried in `.1`.
167    Raw(String, Vec<Value>),
168    /// An explicitly parenthesised expression `(expr)`.
169    Paren(Box<Expr>),
170}
171
172/// A function call.
173#[derive(Clone, Debug, PartialEq)]
174pub struct Func {
175    /// The function name, written verbatim.
176    pub name: &'static str,
177    /// The arguments.
178    pub args: Vec<Expr>,
179    /// Whether `DISTINCT` applies to the first argument, as in aggregates.
180    pub distinct: bool,
181}
182
183impl Func {
184    /// Builds a call to any function.
185    ///
186    /// The name is written verbatim, so this is the escape hatch for engine
187    /// functions that have no dedicated constructor.
188    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    /// Builds `COUNT(expr)`.
197    pub fn count(expr: Expr) -> Expr {
198        Self::call("COUNT", [expr])
199    }
200
201    /// Builds `COUNT(*)`.
202    pub fn count_star() -> Expr {
203        Self::call("COUNT", [Expr::Column(ColumnRef::Asterisk)])
204    }
205
206    /// Builds `COUNT(DISTINCT expr)`.
207    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    /// Builds `MAX(expr)`.
216    pub fn max(expr: Expr) -> Expr {
217        Self::call("MAX", [expr])
218    }
219
220    /// Builds `MIN(expr)`.
221    pub fn min(expr: Expr) -> Expr {
222        Self::call("MIN", [expr])
223    }
224
225    /// Builds `SUM(expr)`.
226    pub fn sum(expr: Expr) -> Expr {
227        Self::call("SUM", [expr])
228    }
229
230    /// Builds `AVG(expr)`.
231    pub fn avg(expr: Expr) -> Expr {
232        Self::call("AVG", [expr])
233    }
234
235    /// Builds `COALESCE(a, b, ...)`.
236    pub fn coalesce(args: impl IntoIterator<Item = Expr>) -> Expr {
237        Self::call("COALESCE", args)
238    }
239
240    /// Builds `LOWER(expr)`.
241    pub fn lower(expr: Expr) -> Expr {
242        Self::call("LOWER", [expr])
243    }
244
245    /// Builds `UPPER(expr)`.
246    pub fn upper(expr: Expr) -> Expr {
247        Self::call("UPPER", [expr])
248    }
249
250    /// Builds `LENGTH(expr)`.
251    pub fn length(expr: Expr) -> Expr {
252        Self::call("LENGTH", [expr])
253    }
254
255    /// Builds `ABS(expr)`.
256    pub fn abs(expr: Expr) -> Expr {
257        Self::call("ABS", [expr])
258    }
259
260    /// Builds `IFNULL(a, b)`.
261    pub fn if_null(a: Expr, b: Expr) -> Expr {
262        Self::call("IFNULL", [a, b])
263    }
264
265    /// Builds `json_extract(json, path)`, binding `path` as a parameter.
266    pub fn json_extract(json: Expr, path: impl Into<Value>) -> Expr {
267        Self::call("json_extract", [json, Expr::Value(path.into())])
268    }
269
270    /// Builds `vector_distance_cos(a, b)` — Turso vector search.
271    pub fn vector_distance_cos(a: Expr, b: Expr) -> Expr {
272        Self::call("vector_distance_cos", [a, b])
273    }
274
275    /// Builds `vector_distance_l2(a, b)` — Turso vector search.
276    pub fn vector_distance_l2(a: Expr, b: Expr) -> Expr {
277        Self::call("vector_distance_l2", [a, b])
278    }
279
280    /// Builds `vector32(text)` — the Turso vector constructor.
281    pub fn vector32(expr: Expr) -> Expr {
282        Self::call("vector32", [expr])
283    }
284
285    /// Builds `fts_match(column, query)` — Turso full-text search, binding
286    /// `query` as a parameter.
287    pub fn fts_match(column: Expr, query: impl Into<Value>) -> Expr {
288        Self::call("fts_match", [column, Expr::Value(query.into())])
289    }
290
291    /// Builds `fts_score(column)` — Turso full-text search ranking.
292    pub fn fts_score(column: Expr) -> Expr {
293        Self::call("fts_score", [column])
294    }
295}
296
297/// Builds a binary node, boxing both operands.
298fn bin(lhs: Expr, op: BinOp, rhs: Expr) -> Expr {
299    Expr::Binary(Box::new(lhs), op, Box::new(rhs))
300}
301
302impl Expr {
303    /// A column reference, from `"name"` or `("table", "name")`.
304    pub fn col(column: impl Into<ColumnRef>) -> Self {
305        Expr::Column(column.into())
306    }
307
308    /// A bound value.
309    pub fn val(value: impl Into<Value>) -> Self {
310        Expr::Value(value.into())
311    }
312
313    /// A tuple of bound values.
314    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    /// A scalar subquery.
319    pub fn subquery(select: Select) -> Self {
320        Expr::Subquery(Box::new(select))
321    }
322
323    /// An `EXISTS (subquery)` test.
324    pub fn exists(select: Select) -> Self {
325        Expr::Exists(Box::new(select))
326    }
327
328    /// Raw SQL with `?` placeholders and no values.
329    pub fn cust(sql: impl Into<String>) -> Self {
330        Expr::Raw(sql.into(), Vec::new())
331    }
332
333    /// Raw SQL with `?` placeholders and the values that fill them, in
334    /// order.
335    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    /// Builds `self = rhs`.
343    pub fn eq(self, rhs: impl Into<Expr>) -> Self {
344        bin(self, BinOp::Eq, rhs.into())
345    }
346
347    /// Builds `self <> rhs`.
348    pub fn ne(self, rhs: impl Into<Expr>) -> Self {
349        bin(self, BinOp::Ne, rhs.into())
350    }
351
352    /// Builds `self < rhs`.
353    pub fn lt(self, rhs: impl Into<Expr>) -> Self {
354        bin(self, BinOp::Lt, rhs.into())
355    }
356
357    /// Builds `self <= rhs`.
358    pub fn lte(self, rhs: impl Into<Expr>) -> Self {
359        bin(self, BinOp::Lte, rhs.into())
360    }
361
362    /// Builds `self > rhs`.
363    pub fn gt(self, rhs: impl Into<Expr>) -> Self {
364        bin(self, BinOp::Gt, rhs.into())
365    }
366
367    /// Builds `self >= rhs`.
368    pub fn gte(self, rhs: impl Into<Expr>) -> Self {
369        bin(self, BinOp::Gte, rhs.into())
370    }
371
372    /// Builds `self AND rhs`.
373    pub fn and(self, rhs: impl Into<Expr>) -> Self {
374        bin(self, BinOp::And, rhs.into())
375    }
376
377    /// Builds `self OR rhs`.
378    pub fn or(self, rhs: impl Into<Expr>) -> Self {
379        bin(self, BinOp::Or, rhs.into())
380    }
381
382    /// Builds `NOT self`.
383    #[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    /// Builds `-self`.
392    #[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    /// Builds `self IS NULL`.
401    pub fn is_null(self) -> Self {
402        Expr::IsNull(Box::new(self))
403    }
404
405    /// Builds `self IS NOT NULL`.
406    pub fn is_not_null(self) -> Self {
407        Expr::IsNotNull(Box::new(self))
408    }
409
410    /// Builds `self IS rhs`, the null-safe equality.
411    pub fn is(self, rhs: impl Into<Expr>) -> Self {
412        bin(self, BinOp::Is, rhs.into())
413    }
414
415    /// Builds `self IS NOT rhs`.
416    pub fn is_not(self, rhs: impl Into<Expr>) -> Self {
417        bin(self, BinOp::IsNot, rhs.into())
418    }
419
420    /// Builds `self LIKE pattern`, binding the pattern as a parameter.
421    ///
422    /// The pattern is taken as written: `%` and `_` are wildcards and no
423    /// `ESCAPE` clause is emitted. See [`like_escaped`](Self::like_escaped)
424    /// for a pattern that needs one.
425    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    /// Builds `self NOT LIKE pattern`, binding the pattern as a parameter.
435    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    /// Builds `self LIKE pattern ESCAPE 'escape'`, for a pattern in which
445    /// `escape` precedes every wildcard that must match literally.
446    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    /// Builds `self MATCH query` for full-text search, binding the query as
456    /// a parameter.
457    pub fn matches(self, query: impl Into<Value>) -> Self {
458        bin(self, BinOp::Match, Expr::Value(query.into()))
459    }
460
461    /// Builds `self IN (values)`.
462    ///
463    /// An empty list renders as `IN (NULL)`, which matches nothing, so a
464    /// filter built from an empty collection behaves as expected instead of
465    /// producing a syntax error.
466    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    /// Builds `self NOT IN (values)`.
471    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    /// Builds `self IN (subquery)`.
476    pub fn in_subquery(self, select: Select) -> Self {
477        Expr::In(Box::new(self), Box::new(Expr::subquery(select)))
478    }
479
480    /// Builds `self NOT IN (subquery)`.
481    pub fn not_in_subquery(self, select: Select) -> Self {
482        Expr::NotIn(Box::new(self), Box::new(Expr::subquery(select)))
483    }
484
485    /// Builds `self BETWEEN a AND b`.
486    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    /// Builds `self NOT BETWEEN a AND b`.
491    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    /// Builds `self + rhs`.
496    #[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    /// Builds `self - rhs`.
505    #[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    /// Builds `self * rhs`.
514    #[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    /// Builds `self / rhs`.
523    #[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    /// Builds `self % rhs`.
532    #[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    /// Builds `self || rhs`.
541    pub fn concat(self, rhs: impl Into<Expr>) -> Self {
542        bin(self, BinOp::Concat, rhs.into())
543    }
544
545    /// Builds `self -> path`, JSON extraction keeping the JSON
546    /// representation.
547    pub fn json_get(self, path: impl Into<Value>) -> Self {
548        bin(self, BinOp::JsonArrow, Expr::Value(path.into()))
549    }
550
551    /// Builds `self ->> path`, JSON extraction yielding a SQL value.
552    pub fn json_get_text(self, path: impl Into<Value>) -> Self {
553        bin(self, BinOp::JsonArrowText, Expr::Value(path.into()))
554    }
555
556    /// Builds `CAST(self AS type)`.
557    pub fn cast_as(self, ty: &'static str) -> Self {
558        Expr::Cast(Box::new(self), ty)
559    }
560
561    /// Builds `self AS alias`.
562    pub fn alias(self, alias: impl IntoIden) -> Self {
563        Expr::Alias(Box::new(self), alias.into_iden())
564    }
565
566    /// Builds `(self)`.
567    pub fn paren(self) -> Self {
568        Expr::Paren(Box::new(self))
569    }
570
571    /// Builds `CASE WHEN ... THEN ... ELSE ... END`.
572    pub fn case(whens: Vec<(Expr, Expr)>, otherwise: Option<Expr>) -> Self {
573        Expr::Case(whens, otherwise.map(Box::new))
574    }
575
576    /// Builds `self LIKE '%s%' ESCAPE '\\'`, with `%`, `_` and `\\` escaped in
577    /// `s` so that the fragment matches literally.
578    pub fn contains(self, s: &str) -> Self {
579        self.like_escaped(format!("%{}%", escape_like(s)), LIKE_ESCAPE)
580    }
581
582    /// Builds `self LIKE 's%' ESCAPE '\\'`, with `%`, `_` and `\\` escaped in
583    /// `s` so that the fragment matches literally.
584    pub fn starts_with(self, s: &str) -> Self {
585        self.like_escaped(format!("{}%", escape_like(s)), LIKE_ESCAPE)
586    }
587
588    /// Builds `self LIKE '%s' ESCAPE '\\'`, with `%`, `_` and `\\` escaped in
589    /// `s` so that the fragment matches literally.
590    pub fn ends_with(self, s: &str) -> Self {
591        self.like_escaped(format!("%{}", escape_like(s)), LIKE_ESCAPE)
592    }
593}
594
595/// The escape character the `contains` family declares in its `ESCAPE`
596/// clause.
597const LIKE_ESCAPE: char = '\\';
598
599/// Escapes the `LIKE` wildcards and the escape character itself in a
600/// user-supplied fragment so that, under `ESCAPE '\\'`, it matches literally.
601fn 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/// A composable `WHERE` / `HAVING` condition.
619///
620/// A condition is an `AND` or `OR` group of parts. Groups nest, so arbitrary
621/// boolean shapes can be built without thinking about precedence: `OR`
622/// groups and negations are parenthesised when flattened. An empty condition
623/// holds trivially and renders as nothing.
624///
625/// ```
626/// use turso_sql::prelude::*;
627///
628/// let cond = Condition::all()
629///     .add(Expr::col("active").eq(true))
630///     .add(Condition::any()
631///         .add(Expr::col("role").eq("admin"))
632///         .add(Expr::col("role").eq("owner")));
633/// ```
634#[derive(Clone, Debug, PartialEq)]
635pub struct Condition {
636    /// Whether the parts are joined with `AND` (`true`) or `OR` (`false`).
637    all: bool,
638    /// Whether the flattened group is wrapped in `NOT`.
639    negate: bool,
640    /// The parts, already flattened to expressions.
641    parts: Vec<Expr>,
642}
643
644impl Condition {
645    /// A condition that holds when every part holds (`AND`).
646    ///
647    /// An empty `all()` holds trivially.
648    pub fn all() -> Self {
649        Self {
650            all: true,
651            negate: false,
652            parts: Vec::new(),
653        }
654    }
655
656    /// A condition that holds when any part holds (`OR`).
657    ///
658    /// An empty `any()` holds trivially rather than failing, so that a
659    /// filter built from an empty list of alternatives does not silently
660    /// exclude every row.
661    pub fn any() -> Self {
662        Self {
663            all: false,
664            negate: false,
665            parts: Vec::new(),
666        }
667    }
668
669    /// Adds a part.
670    ///
671    /// Empty nested conditions are dropped rather than added, so an unused
672    /// sub-group never leaves a stray `()` in the rendered SQL.
673    #[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    /// Adds a part when it is `Some`.
686    #[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    /// Negates the whole condition.
695    #[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    /// Whether no part has been added.
706    pub fn is_empty(&self) -> bool {
707        self.parts.is_empty()
708    }
709
710    /// The number of parts.
711    pub fn len(&self) -> usize {
712        self.parts.len()
713    }
714
715    /// Flattens the condition into one expression, or `None` when empty.
716    pub fn into_expr(self) -> Option<Expr> {
717        let op = if self.all { BinOp::And } else { BinOp::Or };
718        // OR groups and negations are always parenthesised so that nesting
719        // them inside an AND chain keeps the intended precedence explicit.
720        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
740/// Anything usable as a condition: an [`Expr`] or a [`Condition`].
741pub trait IntoCondition {
742    /// Converts into a condition.
743    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    /// Pushes an expression without flattening it, for the single-expression
760    /// conversion above.
761    fn add_expr(mut self, expr: Expr) -> Self {
762        self.parts.push(expr);
763        self
764    }
765}