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    pub(crate) fn to_simple_expr(&self) -> SimpleExpr {
265        let mut inner_exprs = vec![];
266        for ce in &self.conditions {
267            inner_exprs.push(match ce {
268                ConditionExpression::Condition(c) => c.to_simple_expr(),
269                ConditionExpression::SimpleExpr(e) => e.clone(),
270            });
271        }
272        let mut inner_exprs_into_iter = inner_exprs.into_iter();
273        let expr = if let Some(first_expr) = inner_exprs_into_iter.next() {
274            let mut out_expr = first_expr;
275            for e in inner_exprs_into_iter {
276                out_expr = match self.condition_type {
277                    ConditionType::Any => out_expr.or(e),
278                    ConditionType::All => out_expr.and(e),
279                };
280            }
281            out_expr
282        } else {
283            SimpleExpr::Constant(match self.condition_type {
284                ConditionType::Any => false.into(),
285                ConditionType::All => true.into(),
286            })
287        };
288        if self.negate {
289            expr.not()
290        } else {
291            expr
292        }
293    }
294}
295
296impl From<Condition> for ConditionExpression {
297    fn from(condition: Condition) -> Self {
298        ConditionExpression::Condition(condition)
299    }
300}
301
302impl From<SimpleExpr> for ConditionExpression {
303    fn from(condition: SimpleExpr) -> Self {
304        ConditionExpression::SimpleExpr(condition)
305    }
306}
307
308/// Macro to easily create an [`Condition::any`].
309///
310/// # Examples
311///
312/// ```
313/// use sea_query::{*, tests_cfg::*};
314///
315/// let query = Query::select()
316///     .column(Glyph::Image)
317///     .from(Glyph::Table)
318///     .cond_where(
319///         any![
320///             Expr::col((Glyph::Table, Glyph::Aspect)).is_in([3, 4]),
321///             Expr::col((Glyph::Table, Glyph::Image)).like("A%")
322///         ]
323///     )
324///     .to_owned();
325///
326/// assert_eq!(
327///     query.to_string(MysqlQueryBuilder),
328///     r#"SELECT `image` FROM `glyph` WHERE `glyph`.`aspect` IN (3, 4) OR `glyph`.`image` LIKE 'A%'"#
329/// );
330/// ```
331#[macro_export]
332macro_rules! any {
333    ( $( $x:expr ),* $(,)?) => {
334        {
335            let mut tmp = $crate::Condition::any();
336            $(
337                tmp = tmp.add($x);
338            )*
339            tmp
340        }
341    };
342}
343
344/// Macro to easily create an [`Condition::all`].
345///
346/// # Examples
347///
348/// ```
349/// use sea_query::{*, tests_cfg::*};
350///
351/// let query = Query::select()
352///     .column(Glyph::Image)
353///     .from(Glyph::Table)
354///     .cond_where(
355///         all![
356///             Expr::col((Glyph::Table, Glyph::Aspect)).is_in([3, 4]),
357///             Expr::col((Glyph::Table, Glyph::Image)).like("A%")
358///         ]
359///     )
360///     .to_owned();
361///
362/// assert_eq!(
363///     query.to_string(MysqlQueryBuilder),
364///     r#"SELECT `image` FROM `glyph` WHERE `glyph`.`aspect` IN (3, 4) AND `glyph`.`image` LIKE 'A%'"#
365/// );
366#[macro_export]
367macro_rules! all {
368    ( $( $x:expr ),* $(,)?) => {
369        {
370            let mut tmp = $crate::Condition::all();
371            $(
372                tmp = tmp.add($x);
373            )*
374            tmp
375        }
376    };
377}
378
379pub trait ConditionalStatement {
380    /// And where condition.
381    /// Calling `or_where` after `and_where` will panic.
382    ///
383    /// # Examples
384    ///
385    /// ```
386    /// use sea_query::{*, tests_cfg::*};
387    ///
388    /// let query = Query::select()
389    ///     .column(Glyph::Image)
390    ///     .from(Glyph::Table)
391    ///     .and_where(Expr::col((Glyph::Table, Glyph::Aspect)).is_in([3, 4]))
392    ///     .and_where(Expr::col((Glyph::Table, Glyph::Image)).like("A%"))
393    ///     .to_owned();
394    ///
395    /// assert_eq!(
396    ///     query.to_string(MysqlQueryBuilder),
397    ///     r#"SELECT `image` FROM `glyph` WHERE `glyph`.`aspect` IN (3, 4) AND `glyph`.`image` LIKE 'A%'"#
398    /// );
399    /// ```
400    fn and_where(&mut self, other: SimpleExpr) -> &mut Self {
401        self.cond_where(other)
402    }
403
404    /// Optional and where, short hand for `if c.is_some() q.and_where(c)`.
405    ///
406    /// ```
407    /// use sea_query::{tests_cfg::*, *};
408    ///
409    /// let query = Query::select()
410    ///     .column(Glyph::Image)
411    ///     .from(Glyph::Table)
412    ///     .and_where(Expr::col(Glyph::Aspect).is_in([3, 4]))
413    ///     .and_where_option(Some(Expr::col(Glyph::Image).like("A%")))
414    ///     .and_where_option(None)
415    ///     .to_owned();
416    ///
417    /// assert_eq!(
418    ///     query.to_string(MysqlQueryBuilder),
419    ///     r#"SELECT `image` FROM `glyph` WHERE `aspect` IN (3, 4) AND `image` LIKE 'A%'"#
420    /// );
421    /// ```
422    fn and_where_option(&mut self, other: Option<SimpleExpr>) -> &mut Self {
423        if let Some(other) = other {
424            self.and_where(other);
425        }
426        self
427    }
428
429    #[doc(hidden)]
430    // Trait implementation.
431    fn and_or_where(&mut self, condition: LogicalChainOper) -> &mut Self;
432
433    /// Where condition, expressed with `any` and `all`.
434    /// Calling `cond_where` multiple times will conjoin them.
435    /// Calling `or_where` after `cond_where` will panic.
436    ///
437    /// # Examples
438    ///
439    /// ```
440    /// use sea_query::{*, tests_cfg::*};
441    ///
442    /// let query = Query::select()
443    ///     .column(Glyph::Image)
444    ///     .from(Glyph::Table)
445    ///     .cond_where(
446    ///         Cond::all()
447    ///             .add(Expr::col((Glyph::Table, Glyph::Aspect)).is_in([3, 4]))
448    ///             .add(Cond::any()
449    ///                 .add(Expr::col((Glyph::Table, Glyph::Image)).like("A%"))
450    ///                 .add(Expr::col((Glyph::Table, Glyph::Image)).like("B%"))
451    ///             )
452    ///     )
453    ///     .to_owned();
454    ///
455    /// assert_eq!(
456    ///     query.to_string(PostgresQueryBuilder),
457    ///     r#"SELECT "image" FROM "glyph" WHERE "glyph"."aspect" IN (3, 4) AND ("glyph"."image" LIKE 'A%' OR "glyph"."image" LIKE 'B%')"#
458    /// );
459    /// ```
460    ///
461    /// Using macro
462    ///
463    /// ```
464    /// use sea_query::{*, tests_cfg::*};
465    ///
466    /// let query = Query::select()
467    ///     .column(Glyph::Image)
468    ///     .from(Glyph::Table)
469    ///     .cond_where(
470    ///         all![
471    ///             Expr::col((Glyph::Table, Glyph::Aspect)).is_in([3, 4]),
472    ///             any![
473    ///                 Expr::col((Glyph::Table, Glyph::Image)).like("A%"),
474    ///                 Expr::col((Glyph::Table, Glyph::Image)).like("B%"),
475    ///             ]
476    ///         ])
477    ///     .to_owned();
478    ///
479    /// assert_eq!(
480    ///     query.to_string(PostgresQueryBuilder),
481    ///     r#"SELECT "image" FROM "glyph" WHERE "glyph"."aspect" IN (3, 4) AND ("glyph"."image" LIKE 'A%' OR "glyph"."image" LIKE 'B%')"#
482    /// );
483    /// ```
484    ///
485    /// Calling multiple times; the following two are equivalent:
486    ///
487    /// ```
488    /// use sea_query::{tests_cfg::*, *};
489    ///
490    /// assert_eq!(
491    ///     Query::select()
492    ///         .column(Glyph::Id)
493    ///         .from(Glyph::Table)
494    ///         .cond_where(Expr::col(Glyph::Id).eq(1))
495    ///         .cond_where(any![Expr::col(Glyph::Id).eq(2), Expr::col(Glyph::Id).eq(3)])
496    ///         .to_owned()
497    ///         .to_string(PostgresQueryBuilder),
498    ///     r#"SELECT "id" FROM "glyph" WHERE "id" = 1 AND ("id" = 2 OR "id" = 3)"#
499    /// );
500    ///
501    /// assert_eq!(
502    ///     Query::select()
503    ///         .column(Glyph::Id)
504    ///         .from(Glyph::Table)
505    ///         .cond_where(any![Expr::col(Glyph::Id).eq(2), Expr::col(Glyph::Id).eq(3)])
506    ///         .cond_where(Expr::col(Glyph::Id).eq(1))
507    ///         .to_owned()
508    ///         .to_string(PostgresQueryBuilder),
509    ///     r#"SELECT "id" FROM "glyph" WHERE ("id" = 2 OR "id" = 3) AND "id" = 1"#
510    /// );
511    /// ```
512    ///
513    /// Calling multiple times; will be ANDed togother
514    ///
515    /// ```
516    /// use sea_query::{tests_cfg::*, *};
517    ///
518    /// assert_eq!(
519    ///     Query::select()
520    ///         .column(Glyph::Id)
521    ///         .from(Glyph::Table)
522    ///         .cond_where(any![Expr::col(Glyph::Id).eq(1), Expr::col(Glyph::Id).eq(2)])
523    ///         .cond_where(any![Expr::col(Glyph::Id).eq(3), Expr::col(Glyph::Id).eq(4)])
524    ///         .to_owned()
525    ///         .to_string(PostgresQueryBuilder),
526    ///     r#"SELECT "id" FROM "glyph" WHERE ("id" = 1 OR "id" = 2) AND ("id" = 3 OR "id" = 4)"#
527    /// );
528    ///
529    /// assert_eq!(
530    ///     Query::select()
531    ///         .column(Glyph::Id)
532    ///         .from(Glyph::Table)
533    ///         .cond_where(all![Expr::col(Glyph::Id).eq(1), Expr::col(Glyph::Id).eq(2)])
534    ///         .cond_where(all![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 AND "id" = 2 AND "id" = 3 AND "id" = 4"#
538    /// );
539    /// ```
540    ///
541    /// Some more test cases involving negation
542    ///
543    /// ```
544    /// use sea_query::{tests_cfg::*, *};
545    ///
546    /// assert_eq!(
547    ///     Query::select()
548    ///         .column(Glyph::Id)
549    ///         .from(Glyph::Table)
550    ///         .cond_where(
551    ///             Cond::all()
552    ///                 .not()
553    ///                 .add(Expr::col(Glyph::Id).eq(1))
554    ///                 .add(Expr::col(Glyph::Id).eq(2)),
555    ///         )
556    ///         .cond_where(
557    ///             Cond::all()
558    ///                 .add(Expr::col(Glyph::Id).eq(3))
559    ///                 .add(Expr::col(Glyph::Id).eq(4)),
560    ///         )
561    ///         .to_owned()
562    ///         .to_string(PostgresQueryBuilder),
563    ///     r#"SELECT "id" FROM "glyph" WHERE (NOT ("id" = 1 AND "id" = 2)) AND ("id" = 3 AND "id" = 4)"#
564    /// );
565    ///
566    /// assert_eq!(
567    ///     Query::select()
568    ///         .column(Glyph::Id)
569    ///         .from(Glyph::Table)
570    ///         .cond_where(
571    ///             Cond::all()
572    ///                 .add(Expr::col(Glyph::Id).eq(3))
573    ///                 .add(Expr::col(Glyph::Id).eq(4)),
574    ///         )
575    ///         .cond_where(
576    ///             Cond::all()
577    ///                 .not()
578    ///                 .add(Expr::col(Glyph::Id).eq(1))
579    ///                 .add(Expr::col(Glyph::Id).eq(2)),
580    ///         )
581    ///         .to_owned()
582    ///         .to_string(PostgresQueryBuilder),
583    ///     r#"SELECT "id" FROM "glyph" WHERE "id" = 3 AND "id" = 4 AND (NOT ("id" = 1 AND "id" = 2))"#
584    /// );
585    /// ```
586    fn cond_where<C>(&mut self, condition: C) -> &mut Self
587    where
588        C: IntoCondition;
589}
590
591impl IntoCondition for SimpleExpr {
592    fn into_condition(self) -> Condition {
593        Condition::all().add(self)
594    }
595}
596
597impl IntoCondition for Condition {
598    fn into_condition(self) -> Condition {
599        self
600    }
601}
602
603impl ConditionHolder {
604    pub fn new() -> Self {
605        Self::default()
606    }
607
608    pub fn new_with_condition(condition: Condition) -> Self {
609        let contents = ConditionHolderContents::Condition(condition);
610        Self { contents }
611    }
612
613    pub fn is_empty(&self) -> bool {
614        match &self.contents {
615            ConditionHolderContents::Empty => true,
616            ConditionHolderContents::Chain(c) => c.is_empty(),
617            ConditionHolderContents::Condition(c) => c.conditions.is_empty(),
618        }
619    }
620
621    pub fn is_one(&self) -> bool {
622        match &self.contents {
623            ConditionHolderContents::Empty => true,
624            ConditionHolderContents::Chain(c) => c.len() == 1,
625            ConditionHolderContents::Condition(c) => c.conditions.len() == 1,
626        }
627    }
628
629    pub fn add_and_or(&mut self, condition: LogicalChainOper) {
630        match &mut self.contents {
631            ConditionHolderContents::Empty => {
632                self.contents = ConditionHolderContents::Chain(vec![condition])
633            }
634            ConditionHolderContents::Chain(c) => c.push(condition),
635            ConditionHolderContents::Condition(_) => {
636                panic!("Cannot mix `and_where`/`or_where` and `cond_where` in statements")
637            }
638        }
639    }
640
641    pub fn add_condition(&mut self, mut addition: Condition) {
642        match std::mem::take(&mut self.contents) {
643            ConditionHolderContents::Empty => {
644                self.contents = ConditionHolderContents::Condition(addition);
645            }
646            ConditionHolderContents::Condition(mut current) => {
647                if current.condition_type == ConditionType::All && !current.negate {
648                    if addition.condition_type == ConditionType::All && !addition.negate {
649                        current.conditions.append(&mut addition.conditions);
650                        self.contents = ConditionHolderContents::Condition(current);
651                    } else {
652                        self.contents = ConditionHolderContents::Condition(current.add(addition));
653                    }
654                } else {
655                    self.contents = ConditionHolderContents::Condition(
656                        Condition::all().add(current).add(addition),
657                    );
658                }
659            }
660            ConditionHolderContents::Chain(_) => {
661                panic!("Cannot mix `and_where`/`or_where` and `cond_where` in statements")
662            }
663        }
664    }
665}
666
667#[cfg(test)]
668mod test {
669    use crate::{tests_cfg::*, *};
670    use pretty_assertions::assert_eq;
671
672    #[test]
673    #[cfg(feature = "backend-mysql")]
674    fn test_blank_condition() {
675        let query = Query::select()
676            .column(Glyph::Image)
677            .from(Glyph::Table)
678            .cond_where(Cond::all())
679            .cond_where(Expr::val(1).eq(1))
680            .cond_where(Expr::val(2).eq(2))
681            .cond_where(Cond::any().add(Expr::val(3).eq(3)).add(Expr::val(4).eq(4)))
682            .to_owned();
683
684        assert_eq!(
685            query.to_string(MysqlQueryBuilder),
686            "SELECT `image` FROM `glyph` WHERE 1 = 1 AND 2 = 2 AND (3 = 3 OR 4 = 4)"
687        );
688    }
689}