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}