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}