sea_query/query/
condition.rs

1use crate::{expr::SimpleExpr, types::LogicalChainOper};
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4pub enum ConditionType {
5    Any,
6    All,
7}
8
9/// Represents the value of an [`Condition::any`] or [`Condition::all`]: a set of disjunctive or conjunctive conditions.
10#[derive(Debug, Clone, PartialEq)]
11pub struct Condition {
12    pub(crate) negate: bool,
13    pub(crate) condition_type: ConditionType,
14    pub(crate) conditions: Vec<ConditionExpression>,
15}
16
17pub trait IntoCondition {
18    fn into_condition(self) -> Condition;
19}
20
21pub type Cond = Condition;
22
23/// Represents anything that can be passed to an [`Condition::any`] or [`Condition::all`]'s [`Condition::add`] method.
24///
25/// The arguments are automatically converted to the right enum.
26#[derive(Debug, Clone, PartialEq)]
27pub enum ConditionExpression {
28    Condition(Condition),
29    SimpleExpr(SimpleExpr),
30}
31
32#[derive(Default, Debug, Clone, PartialEq)]
33pub enum ConditionHolderContents {
34    #[default]
35    Empty,
36    Chain(Vec<LogicalChainOper>),
37    Condition(Condition),
38}
39
40#[derive(Default, Debug, Clone, PartialEq)]
41pub struct ConditionHolder {
42    pub contents: ConditionHolderContents,
43}
44
45impl Condition {
46    /// Add a condition to the set.
47    ///
48    /// If it's an [`Condition::any`], it will be separated from the others by an `" OR "` in the query. If it's
49    /// an [`Condition::all`], it will be separated by an `" AND "`.
50    ///
51    /// ```
52    /// use sea_query::{tests_cfg::*, *};
53    ///
54    /// let statement = Query::select()
55    ///     .column(Glyph::Id)
56    ///     .from(Glyph::Table)
57    ///     .cond_where(
58    ///         Cond::all()
59    ///             .add(Expr::col(Glyph::Aspect).eq(0).into_condition().not())
60    ///             .add(Expr::col(Glyph::Id).eq(0).into_condition().not()),
61    ///     )
62    ///     .to_string(PostgresQueryBuilder);
63    /// assert_eq!(
64    ///     statement,
65    ///     r#"SELECT "id" FROM "glyph" WHERE (NOT "aspect" = 0) AND (NOT "id" = 0)"#
66    /// );
67    /// ```
68    #[allow(clippy::should_implement_trait)]
69    pub fn add<C>(mut self, condition: C) -> Self
70    where
71        C: Into<ConditionExpression>,
72    {
73        let mut expr: ConditionExpression = condition.into();
74        if let ConditionExpression::Condition(ref mut c) = expr {
75            // Skip the junction if there is only one.
76            if c.conditions.len() == 1 && !c.negate {
77                expr = c.conditions.pop().unwrap();
78            }
79        }
80        self.conditions.push(expr);
81        self
82    }
83
84    /// Add an optional condition to the set.
85    ///
86    /// Shorthand for `if o.is_some() { self.add(o) }`
87    ///
88    /// # Examples
89    ///
90    /// ```
91    /// use sea_query::{tests_cfg::*, *};
92    ///
93    /// let query = Query::select()
94    ///     .column(Glyph::Image)
95    ///     .from(Glyph::Table)
96    ///     .cond_where(
97    ///         Cond::all()
98    ///             .add_option(Some(Expr::col((Glyph::Table, Glyph::Image)).like("A%")))
99    ///             .add_option(None::<SimpleExpr>),
100    ///     )
101    ///     .to_owned();
102    ///
103    /// assert_eq!(
104    ///     query.to_string(MysqlQueryBuilder),
105    ///     r#"SELECT `image` FROM `glyph` WHERE `glyph`.`image` LIKE 'A%'"#
106    /// );
107    /// ```
108    #[allow(clippy::should_implement_trait)]
109    pub fn add_option<C>(self, other: Option<C>) -> Self
110    where
111        C: Into<ConditionExpression>,
112    {
113        if let Some(other) = other {
114            self.add(other)
115        } else {
116            self
117        }
118    }
119
120    /// Create a condition that is true if any of the conditions is true.
121    ///
122    /// # Examples
123    ///
124    /// ```
125    /// use sea_query::{*, tests_cfg::*};
126    ///
127    /// let query = Query::select()
128    ///     .column(Glyph::Image)
129    ///     .from(Glyph::Table)
130    ///     .cond_where(
131    ///         Cond::any()
132    ///             .add(Expr::col((Glyph::Table, Glyph::Aspect)).is_in([3, 4]))
133    ///             .add(Expr::col((Glyph::Table, Glyph::Image)).like("A%"))
134    ///     )
135    ///     .to_owned();
136    ///
137    /// assert_eq!(
138    ///     query.to_string(MysqlQueryBuilder),
139    ///     r#"SELECT `image` FROM `glyph` WHERE `glyph`.`aspect` IN (3, 4) OR `glyph`.`image` LIKE 'A%'"#
140    /// );
141    /// ```
142    pub fn any() -> Condition {
143        Condition {
144            negate: false,
145            condition_type: ConditionType::Any,
146            conditions: Vec::new(),
147        }
148    }
149
150    /// Create a condition that is false if any of the conditions is false.
151    ///
152    /// # Examples
153    ///
154    /// ```
155    /// use sea_query::{*, tests_cfg::*};
156    ///
157    /// let query = Query::select()
158    ///     .column(Glyph::Image)
159    ///     .from(Glyph::Table)
160    ///     .cond_where(
161    ///         Cond::all()
162    ///             .add(Expr::col((Glyph::Table, Glyph::Aspect)).is_in([3, 4]))
163    ///             .add(Expr::col((Glyph::Table, Glyph::Image)).like("A%"))
164    ///     )
165    ///     .to_owned();
166    ///
167    /// assert_eq!(
168    ///     query.to_string(MysqlQueryBuilder),
169    ///     r#"SELECT `image` FROM `glyph` WHERE `glyph`.`aspect` IN (3, 4) AND `glyph`.`image` LIKE 'A%'"#
170    /// );
171    /// ```
172    pub fn all() -> Condition {
173        Condition {
174            negate: false,
175            condition_type: ConditionType::All,
176            conditions: Vec::new(),
177        }
178    }
179
180    /// Negates a condition.
181    ///
182    /// # Examples
183    ///
184    /// ```
185    /// use sea_query::{tests_cfg::*, *};
186    ///
187    /// let query = Query::select()
188    ///     .column(Glyph::Image)
189    ///     .from(Glyph::Table)
190    ///     .cond_where(
191    ///         Cond::all()
192    ///             .not()
193    ///             .add(Expr::col((Glyph::Table, Glyph::Aspect)).is_in([3, 4]))
194    ///             .add(Expr::col((Glyph::Table, Glyph::Image)).like("A%"))
195    ///     )
196    ///     .to_owned();
197    ///
198    /// assert_eq!(
199    ///     query.to_string(MysqlQueryBuilder),
200    ///     r#"SELECT `image` FROM `glyph` WHERE NOT (`glyph`.`aspect` IN (3, 4) AND `glyph`.`image` LIKE 'A%')"#
201    /// );
202    /// ```
203    ///
204    /// # More Examples
205    ///
206    /// ```
207    /// use sea_query::{tests_cfg::*, *};
208    ///
209    /// let query = Query::select()
210    ///     .column(Glyph::Id)
211    ///     .cond_where(
212    ///         Cond::all()
213    ///             .add(
214    ///                 Cond::all()
215    ///                     .not()
216    ///                     .add(Expr::val(1).eq(1))
217    ///                     .add(Expr::val(2).eq(2)),
218    ///             )
219    ///             .add(Cond::any().add(Expr::val(3).eq(3)).add(Expr::val(4).eq(4))),
220    ///     )
221    ///     .to_owned();
222    ///
223    /// assert_eq!(
224    ///     query.to_string(MysqlQueryBuilder),
225    ///     r#"SELECT `id` WHERE (NOT (1 = 1 AND 2 = 2)) AND (3 = 3 OR 4 = 4)"#
226    /// );
227    /// ```
228    #[allow(clippy::should_implement_trait)]
229    pub fn not(mut self) -> Self {
230        self.negate = !self.negate;
231        self
232    }
233
234    /// Whether or not any condition has been added
235    ///
236    /// # Examples
237    ///
238    /// ```
239    /// use sea_query::{tests_cfg::*, *};
240    ///
241    /// let is_empty = Cond::all().is_empty();
242    ///
243    /// assert!(is_empty);
244    /// ```
245    pub fn is_empty(&self) -> bool {
246        self.conditions.is_empty()
247    }
248
249    /// How many conditions were added
250    ///
251    /// # Examples
252    ///
253    /// ```
254    /// use sea_query::{tests_cfg::*, *};
255    ///
256    /// let len = Cond::all().len();
257    ///
258    /// assert_eq!(len, 0);
259    /// ```
260    pub fn len(&self) -> usize {
261        self.conditions.len()
262    }
263}
264
265impl From<Condition> for SimpleExpr {
266    fn from(cond: Condition) -> Self {
267        let mut inner_exprs = vec![];
268        for ce in cond.conditions {
269            inner_exprs.push(match ce {
270                ConditionExpression::Condition(c) => c.into(),
271                ConditionExpression::SimpleExpr(e) => e,
272            });
273        }
274        let mut inner_exprs_into_iter = inner_exprs.into_iter();
275        let expr = if let Some(first_expr) = inner_exprs_into_iter.next() {
276            let mut out_expr = first_expr;
277            for e in inner_exprs_into_iter {
278                out_expr = match cond.condition_type {
279                    ConditionType::Any => out_expr.or(e),
280                    ConditionType::All => out_expr.and(e),
281                };
282            }
283            out_expr
284        } else {
285            SimpleExpr::Constant(match cond.condition_type {
286                ConditionType::Any => false.into(),
287                ConditionType::All => true.into(),
288            })
289        };
290        if cond.negate {
291            expr.not()
292        } else {
293            expr
294        }
295    }
296}
297
298impl From<ConditionExpression> for SimpleExpr {
299    fn from(ce: ConditionExpression) -> Self {
300        match ce {
301            ConditionExpression::Condition(c) => c.into(),
302            ConditionExpression::SimpleExpr(e) => e,
303        }
304    }
305}
306
307impl From<Condition> for ConditionExpression {
308    fn from(condition: Condition) -> Self {
309        ConditionExpression::Condition(condition)
310    }
311}
312
313impl From<SimpleExpr> for ConditionExpression {
314    fn from(condition: SimpleExpr) -> Self {
315        ConditionExpression::SimpleExpr(condition)
316    }
317}
318
319/// Macro to easily create an [`Condition::any`].
320///
321/// # Examples
322///
323/// ```
324/// use sea_query::{*, tests_cfg::*};
325///
326/// let query = Query::select()
327///     .column(Glyph::Image)
328///     .from(Glyph::Table)
329///     .cond_where(
330///         any![
331///             Expr::col((Glyph::Table, Glyph::Aspect)).is_in([3, 4]),
332///             Expr::col((Glyph::Table, Glyph::Image)).like("A%")
333///         ]
334///     )
335///     .to_owned();
336///
337/// assert_eq!(
338///     query.to_string(MysqlQueryBuilder),
339///     r#"SELECT `image` FROM `glyph` WHERE `glyph`.`aspect` IN (3, 4) OR `glyph`.`image` LIKE 'A%'"#
340/// );
341/// ```
342#[macro_export]
343macro_rules! any {
344    ( $( $x:expr ),* $(,)?) => {
345        {
346            let mut tmp = $crate::Condition::any();
347            $(
348                tmp = tmp.add($x);
349            )*
350            tmp
351        }
352    };
353}
354
355/// Macro to easily create an [`Condition::all`].
356///
357/// # Examples
358///
359/// ```
360/// use sea_query::{*, tests_cfg::*};
361///
362/// let query = Query::select()
363///     .column(Glyph::Image)
364///     .from(Glyph::Table)
365///     .cond_where(
366///         all![
367///             Expr::col((Glyph::Table, Glyph::Aspect)).is_in([3, 4]),
368///             Expr::col((Glyph::Table, Glyph::Image)).like("A%")
369///         ]
370///     )
371///     .to_owned();
372///
373/// assert_eq!(
374///     query.to_string(MysqlQueryBuilder),
375///     r#"SELECT `image` FROM `glyph` WHERE `glyph`.`aspect` IN (3, 4) AND `glyph`.`image` LIKE 'A%'"#
376/// );
377#[macro_export]
378macro_rules! all {
379    ( $( $x:expr ),* $(,)?) => {
380        {
381            let mut tmp = $crate::Condition::all();
382            $(
383                tmp = tmp.add($x);
384            )*
385            tmp
386        }
387    };
388}
389
390pub trait ConditionalStatement {
391    /// And where condition.
392    /// Calling `or_where` after `and_where` will panic.
393    ///
394    /// # Examples
395    ///
396    /// ```
397    /// use sea_query::{*, tests_cfg::*};
398    ///
399    /// let query = Query::select()
400    ///     .column(Glyph::Image)
401    ///     .from(Glyph::Table)
402    ///     .and_where(Expr::col((Glyph::Table, Glyph::Aspect)).is_in([3, 4]))
403    ///     .and_where(Expr::col((Glyph::Table, Glyph::Image)).like("A%"))
404    ///     .to_owned();
405    ///
406    /// assert_eq!(
407    ///     query.to_string(MysqlQueryBuilder),
408    ///     r#"SELECT `image` FROM `glyph` WHERE `glyph`.`aspect` IN (3, 4) AND `glyph`.`image` LIKE 'A%'"#
409    /// );
410    /// ```
411    fn and_where(&mut self, other: SimpleExpr) -> &mut Self {
412        self.cond_where(other)
413    }
414
415    /// Optional and where, short hand for `if c.is_some() q.and_where(c)`.
416    ///
417    /// ```
418    /// use sea_query::{tests_cfg::*, *};
419    ///
420    /// let query = Query::select()
421    ///     .column(Glyph::Image)
422    ///     .from(Glyph::Table)
423    ///     .and_where(Expr::col(Glyph::Aspect).is_in([3, 4]))
424    ///     .and_where_option(Some(Expr::col(Glyph::Image).like("A%")))
425    ///     .and_where_option(None)
426    ///     .to_owned();
427    ///
428    /// assert_eq!(
429    ///     query.to_string(MysqlQueryBuilder),
430    ///     r#"SELECT `image` FROM `glyph` WHERE `aspect` IN (3, 4) AND `image` LIKE 'A%'"#
431    /// );
432    /// ```
433    fn and_where_option(&mut self, other: Option<SimpleExpr>) -> &mut Self {
434        if let Some(other) = other {
435            self.and_where(other);
436        }
437        self
438    }
439
440    #[doc(hidden)]
441    // Trait implementation.
442    fn and_or_where(&mut self, condition: LogicalChainOper) -> &mut Self;
443
444    /// Where condition, expressed with `any` and `all`.
445    /// Calling `cond_where` multiple times will conjoin them.
446    /// Calling `or_where` after `cond_where` will panic.
447    ///
448    /// # Examples
449    ///
450    /// ```
451    /// use sea_query::{*, tests_cfg::*};
452    ///
453    /// let query = Query::select()
454    ///     .column(Glyph::Image)
455    ///     .from(Glyph::Table)
456    ///     .cond_where(
457    ///         Cond::all()
458    ///             .add(Expr::col((Glyph::Table, Glyph::Aspect)).is_in([3, 4]))
459    ///             .add(Cond::any()
460    ///                 .add(Expr::col((Glyph::Table, Glyph::Image)).like("A%"))
461    ///                 .add(Expr::col((Glyph::Table, Glyph::Image)).like("B%"))
462    ///             )
463    ///     )
464    ///     .to_owned();
465    ///
466    /// assert_eq!(
467    ///     query.to_string(PostgresQueryBuilder),
468    ///     r#"SELECT "image" FROM "glyph" WHERE "glyph"."aspect" IN (3, 4) AND ("glyph"."image" LIKE 'A%' OR "glyph"."image" LIKE 'B%')"#
469    /// );
470    /// ```
471    ///
472    /// Using macro
473    ///
474    /// ```
475    /// use sea_query::{*, tests_cfg::*};
476    ///
477    /// let query = Query::select()
478    ///     .column(Glyph::Image)
479    ///     .from(Glyph::Table)
480    ///     .cond_where(
481    ///         all![
482    ///             Expr::col((Glyph::Table, Glyph::Aspect)).is_in([3, 4]),
483    ///             any![
484    ///                 Expr::col((Glyph::Table, Glyph::Image)).like("A%"),
485    ///                 Expr::col((Glyph::Table, Glyph::Image)).like("B%"),
486    ///             ]
487    ///         ])
488    ///     .to_owned();
489    ///
490    /// assert_eq!(
491    ///     query.to_string(PostgresQueryBuilder),
492    ///     r#"SELECT "image" FROM "glyph" WHERE "glyph"."aspect" IN (3, 4) AND ("glyph"."image" LIKE 'A%' OR "glyph"."image" LIKE 'B%')"#
493    /// );
494    /// ```
495    ///
496    /// Calling multiple times; the following two are equivalent:
497    ///
498    /// ```
499    /// use sea_query::{tests_cfg::*, *};
500    ///
501    /// assert_eq!(
502    ///     Query::select()
503    ///         .column(Glyph::Id)
504    ///         .from(Glyph::Table)
505    ///         .cond_where(Expr::col(Glyph::Id).eq(1))
506    ///         .cond_where(any![Expr::col(Glyph::Id).eq(2), Expr::col(Glyph::Id).eq(3)])
507    ///         .to_owned()
508    ///         .to_string(PostgresQueryBuilder),
509    ///     r#"SELECT "id" FROM "glyph" WHERE "id" = 1 AND ("id" = 2 OR "id" = 3)"#
510    /// );
511    ///
512    /// assert_eq!(
513    ///     Query::select()
514    ///         .column(Glyph::Id)
515    ///         .from(Glyph::Table)
516    ///         .cond_where(any![Expr::col(Glyph::Id).eq(2), Expr::col(Glyph::Id).eq(3)])
517    ///         .cond_where(Expr::col(Glyph::Id).eq(1))
518    ///         .to_owned()
519    ///         .to_string(PostgresQueryBuilder),
520    ///     r#"SELECT "id" FROM "glyph" WHERE ("id" = 2 OR "id" = 3) AND "id" = 1"#
521    /// );
522    /// ```
523    ///
524    /// Calling multiple times; will be ANDed togother
525    ///
526    /// ```
527    /// use sea_query::{tests_cfg::*, *};
528    ///
529    /// assert_eq!(
530    ///     Query::select()
531    ///         .column(Glyph::Id)
532    ///         .from(Glyph::Table)
533    ///         .cond_where(any![Expr::col(Glyph::Id).eq(1), Expr::col(Glyph::Id).eq(2)])
534    ///         .cond_where(any![Expr::col(Glyph::Id).eq(3), Expr::col(Glyph::Id).eq(4)])
535    ///         .to_owned()
536    ///         .to_string(PostgresQueryBuilder),
537    ///     r#"SELECT "id" FROM "glyph" WHERE ("id" = 1 OR "id" = 2) AND ("id" = 3 OR "id" = 4)"#
538    /// );
539    ///
540    /// assert_eq!(
541    ///     Query::select()
542    ///         .column(Glyph::Id)
543    ///         .from(Glyph::Table)
544    ///         .cond_where(all![Expr::col(Glyph::Id).eq(1), Expr::col(Glyph::Id).eq(2)])
545    ///         .cond_where(all![Expr::col(Glyph::Id).eq(3), Expr::col(Glyph::Id).eq(4)])
546    ///         .to_owned()
547    ///         .to_string(PostgresQueryBuilder),
548    ///     r#"SELECT "id" FROM "glyph" WHERE "id" = 1 AND "id" = 2 AND "id" = 3 AND "id" = 4"#
549    /// );
550    /// ```
551    ///
552    /// Some more test cases involving negation
553    ///
554    /// ```
555    /// use sea_query::{tests_cfg::*, *};
556    ///
557    /// assert_eq!(
558    ///     Query::select()
559    ///         .column(Glyph::Id)
560    ///         .from(Glyph::Table)
561    ///         .cond_where(
562    ///             Cond::all()
563    ///                 .not()
564    ///                 .add(Expr::col(Glyph::Id).eq(1))
565    ///                 .add(Expr::col(Glyph::Id).eq(2)),
566    ///         )
567    ///         .cond_where(
568    ///             Cond::all()
569    ///                 .add(Expr::col(Glyph::Id).eq(3))
570    ///                 .add(Expr::col(Glyph::Id).eq(4)),
571    ///         )
572    ///         .to_owned()
573    ///         .to_string(PostgresQueryBuilder),
574    ///     r#"SELECT "id" FROM "glyph" WHERE (NOT ("id" = 1 AND "id" = 2)) AND ("id" = 3 AND "id" = 4)"#
575    /// );
576    ///
577    /// assert_eq!(
578    ///     Query::select()
579    ///         .column(Glyph::Id)
580    ///         .from(Glyph::Table)
581    ///         .cond_where(
582    ///             Cond::all()
583    ///                 .add(Expr::col(Glyph::Id).eq(3))
584    ///                 .add(Expr::col(Glyph::Id).eq(4)),
585    ///         )
586    ///         .cond_where(
587    ///             Cond::all()
588    ///                 .not()
589    ///                 .add(Expr::col(Glyph::Id).eq(1))
590    ///                 .add(Expr::col(Glyph::Id).eq(2)),
591    ///         )
592    ///         .to_owned()
593    ///         .to_string(PostgresQueryBuilder),
594    ///     r#"SELECT "id" FROM "glyph" WHERE "id" = 3 AND "id" = 4 AND (NOT ("id" = 1 AND "id" = 2))"#
595    /// );
596    /// ```
597    fn cond_where<C>(&mut self, condition: C) -> &mut Self
598    where
599        C: IntoCondition;
600}
601
602impl IntoCondition for SimpleExpr {
603    fn into_condition(self) -> Condition {
604        Condition::all().add(self)
605    }
606}
607
608impl IntoCondition for Condition {
609    fn into_condition(self) -> Condition {
610        self
611    }
612}
613
614impl ConditionHolder {
615    pub fn new() -> Self {
616        Self::default()
617    }
618
619    pub fn new_with_condition(condition: Condition) -> Self {
620        let contents = ConditionHolderContents::Condition(condition);
621        Self { contents }
622    }
623
624    pub fn is_empty(&self) -> bool {
625        match &self.contents {
626            ConditionHolderContents::Empty => true,
627            ConditionHolderContents::Chain(c) => c.is_empty(),
628            ConditionHolderContents::Condition(c) => c.conditions.is_empty(),
629        }
630    }
631
632    pub fn is_one(&self) -> bool {
633        match &self.contents {
634            ConditionHolderContents::Empty => true,
635            ConditionHolderContents::Chain(c) => c.len() == 1,
636            ConditionHolderContents::Condition(c) => c.conditions.len() == 1,
637        }
638    }
639
640    pub fn add_and_or(&mut self, condition: LogicalChainOper) {
641        match &mut self.contents {
642            ConditionHolderContents::Empty => {
643                self.contents = ConditionHolderContents::Chain(vec![condition])
644            }
645            ConditionHolderContents::Chain(c) => c.push(condition),
646            ConditionHolderContents::Condition(_) => {
647                panic!("Cannot mix `and_where`/`or_where` and `cond_where` in statements")
648            }
649        }
650    }
651
652    pub fn add_condition(&mut self, mut addition: Condition) {
653        match std::mem::take(&mut self.contents) {
654            ConditionHolderContents::Empty => {
655                self.contents = ConditionHolderContents::Condition(addition);
656            }
657            ConditionHolderContents::Condition(mut current) => {
658                if current.condition_type == ConditionType::All && !current.negate {
659                    if addition.condition_type == ConditionType::All && !addition.negate {
660                        current.conditions.append(&mut addition.conditions);
661                        self.contents = ConditionHolderContents::Condition(current);
662                    } else {
663                        self.contents = ConditionHolderContents::Condition(current.add(addition));
664                    }
665                } else {
666                    self.contents = ConditionHolderContents::Condition(
667                        Condition::all().add(current).add(addition),
668                    );
669                }
670            }
671            ConditionHolderContents::Chain(_) => {
672                panic!("Cannot mix `and_where`/`or_where` and `cond_where` in statements")
673            }
674        }
675    }
676}
677
678#[cfg(test)]
679mod test {
680    use crate::{tests_cfg::*, *};
681    use pretty_assertions::assert_eq;
682
683    #[test]
684    #[cfg(feature = "backend-mysql")]
685    fn test_blank_condition() {
686        let query = Query::select()
687            .column(Glyph::Image)
688            .from(Glyph::Table)
689            .cond_where(Cond::all())
690            .cond_where(Expr::val(1).eq(1))
691            .cond_where(Expr::val(2).eq(2))
692            .cond_where(Cond::any().add(Expr::val(3).eq(3)).add(Expr::val(4).eq(4)))
693            .to_owned();
694
695        assert_eq!(
696            query.to_string(MysqlQueryBuilder),
697            "SELECT `image` FROM `glyph` WHERE 1 = 1 AND 2 = 2 AND (3 = 3 OR 4 = 4)"
698        );
699    }
700}