Skip to main content

vantage_sql/primitives/
logical.rs

1//! `or_()` and `and_()`, and the [`ConditionGroup`] they make.
2//!
3//! A group writes one set of brackets around its whole chain and none around
4//! each operand. A chain stays flat while the operator does not change:
5//! `a.or_(b).or_(c)` gives `(a OR b OR c)`. Nesting makes a different group:
6//! `a.or_(b.or_(c))` gives `(a OR (b OR c))`.
7//!
8//! The group writes the brackets, not the statement renderer: `WHERE` joins
9//! its conditions with a plain `AND`, so a search on top of a table's
10//! conditions reads `role = 'admin' AND (a OR b)`. Without the group it would
11//! read `role = 'admin' AND a OR b`, which means `(role = 'admin' AND a) OR b`.
12
13use vantage_expressions::{Expression, Expressive, ExpressiveEnum};
14
15/// Conditions that one logical operator joins, written as a single
16/// group in brackets. To make a group, call [`or_`] or [`and_`], or use
17/// the `or_` and `and_` methods on a column or an identifier.
18#[derive(Clone)]
19pub struct ConditionGroup<T> {
20    operator: &'static str,
21    operands: Vec<Expression<T>>,
22}
23
24impl<T> std::fmt::Debug for ConditionGroup<T> {
25    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26        f.debug_struct("ConditionGroup")
27            .field("operator", &self.operator)
28            .field("operands", &self.operands.len())
29            .finish()
30    }
31}
32
33impl<T: Clone> ConditionGroup<T> {
34    pub(crate) fn new(operator: &'static str, operands: Vec<Expression<T>>) -> Self {
35        Self { operator, operands }
36    }
37
38    /// Adds a condition with `OR`. If this group is an `OR` chain, the
39    /// condition goes into the same group.
40    ///
41    /// This method is inherent. Rust selects it before the `or_` of the
42    /// operation trait, and this keeps a chain flat.
43    pub fn or_(self, other: impl Expressive<T>) -> Self {
44        self.extend("OR", other.expr())
45    }
46
47    /// Adds a condition with `AND`. If this group is an `AND` chain, the
48    /// condition goes into the same group.
49    pub fn and_(self, other: impl Expressive<T>) -> Self {
50        self.extend("AND", other.expr())
51    }
52
53    fn extend(mut self, operator: &'static str, other: Expression<T>) -> Self {
54        if self.operator == operator {
55            self.operands.push(other);
56            self
57        } else {
58            // The operator changes. The chain to this point becomes one
59            // operand of the new group. It keeps its brackets and its
60            // meaning.
61            Self::new(operator, vec![self.expr(), other])
62        }
63    }
64}
65
66impl<T: Clone> Expressive<T> for ConditionGroup<T> {
67    fn expr(&self) -> Expression<T> {
68        let separator = format!(" {} ", self.operator);
69        let template = std::iter::repeat_n("{}", self.operands.len())
70            .collect::<Vec<_>>()
71            .join(&separator);
72        Expression::new(
73            format!("({template})"),
74            self.operands
75                .iter()
76                .cloned()
77                .map(ExpressiveEnum::Nested)
78                .collect(),
79        )
80    }
81}
82
83/// Joins two conditions with `OR`: `(lhs OR rhs)`.
84///
85/// ```ignore
86/// use vantage_sql::primitives::*;
87///
88/// or_(ident("role").eq("admin"), ident("role").eq("superuser"))
89/// // => ("role" = 'admin' OR "role" = 'superuser')
90/// ```
91pub fn or_<T: Clone>(lhs: impl Expressive<T>, rhs: impl Expressive<T>) -> ConditionGroup<T> {
92    ConditionGroup::new("OR", vec![lhs.expr(), rhs.expr()])
93}
94
95/// Joins two conditions with `AND`: `(lhs AND rhs)`.
96///
97/// `with_condition()` joins its conditions with `AND` already. Use
98/// `and_()` when you must make a group inside an `or_()`:
99///
100/// ```ignore
101/// use vantage_sql::primitives::*;
102///
103/// // ((price > 100 AND in_stock = 1) OR featured = 1)
104/// or_(
105///     and_(ident("price").gt(100), ident("in_stock").eq(true)),
106///     ident("featured").eq(true),
107/// )
108/// ```
109pub fn and_<T: Clone>(lhs: impl Expressive<T>, rhs: impl Expressive<T>) -> ConditionGroup<T> {
110    ConditionGroup::new("AND", vec![lhs.expr(), rhs.expr()])
111}