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