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}