Skip to main content

drizzle_core/expr/
logical.rs

1//! Logical operators: `AND`, `OR` and `NOT`.
2//!
3//! Use the functions [`and`], [`or`] and [`not`], or the Rust operators `&`,
4//! `|` and `!` on [`SQLExpr`] values. For more than two conditions, see
5//! [`all`](super::all), [`any`](super::any) and condition tuples.
6//!
7//! Operands must be boolean expressions. The result is nullable if any operand
8//! is nullable, and is an aggregate if any operand is.
9//!
10//! # Examples
11//!
12//! ```rust
13//! # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
14//! # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
15//! # #[derive(Clone, Debug)] struct Value(String);
16//! # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
17//! # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
18//! # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
19//! # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
20//! # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
21//! # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
22//! # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
23//! # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
24//! let a = and(gt(users.age, 18), is_not_null(users.email));
25//! let b = gt(users.age, 18) & is_not_null(users.email);
26//! assert_eq!(a.sql(), b.sql());
27//! assert_eq!(a.sql(), r#"("users"."age" > ? AND "users"."email" IS NOT NULL)"#);
28//!
29//! let c = !eq(users.name, "admin") | lt(users.age, 13);
30//! assert_eq!(c.sql(), r#"(NOT ("users"."name" = ?) OR "users"."age" < ?)"#);
31//! ```
32
33use core::ops::{BitAnd, BitOr, Not};
34
35use crate::dialect::DialectTypes;
36use crate::sql::{SQL, SQLChunk, Token};
37use crate::traits::SQLParam;
38use crate::types::BooleanLike;
39
40use super::{AggregateKind, Expr, Nullability, SQLExpr};
41
42/// Renders `(left <token> right)`. Like an element of a condition list
43/// ([`all`](super::all), [`any`](super::any)), a `None` operand contributes
44/// nothing: `and(x, None)` renders `(x)`, and with both operands `None` the
45/// result is `empty`.
46#[inline]
47fn binary_logical_op<'a, V, L, R>(
48    left: L,
49    token: Token,
50    right: R,
51    empty: &'static str,
52) -> SQL<'a, V>
53where
54    V: SQLParam + 'a,
55    L: Expr<'a, V>,
56    L::SQLType: BooleanLike,
57    R: Expr<'a, V>,
58    R::SQLType: BooleanLike,
59{
60    match (left.into_condition_sql(), right.into_condition_sql()) {
61        (Some(left), Some(right)) => SQL::from(Token::LPAREN)
62            .append(left)
63            .push(token)
64            .append(right)
65            .push(Token::RPAREN),
66        (Some(operand), None) | (None, Some(operand)) => operand.parens(),
67        (None, None) => SQL::raw(empty),
68    }
69}
70
71// =============================================================================
72// NOT
73// =============================================================================
74
75/// Logical negation (`NOT`).
76///
77/// Renders `NOT (expr)`; a single raw word, identifier or number is not
78/// parenthesized. The operand must be boolean. The result keeps the operand's
79/// nullability and aggregate kind. `!expr` on an [`SQLExpr`] does the same.
80///
81/// # Examples
82///
83/// ```rust
84/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
85/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
86/// # #[derive(Clone, Debug)] struct Value(String);
87/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
88/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
89/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
90/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
91/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
92/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
93/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
94/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
95/// assert_eq!(not(users.active).sql(), r#"NOT ("users"."active")"#);
96/// assert_eq!(not(gt(users.age, 18)).sql(), r#"NOT ("users"."age" > ?)"#);
97/// ```
98///
99/// # Type safety
100///
101/// Negating a text column does not compile:
102///
103/// ```rust,compile_fail
104/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
105/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
106/// # #[derive(Clone, Debug)] struct Value(String);
107/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
108/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
109/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
110/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
111/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
112/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
113/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
114/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
115/// let wrong = not(users.name);
116/// ```
117#[allow(clippy::type_complexity)]
118pub fn not<'a, V, E>(
119    expr: E,
120) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, E::Nullable, E::Aggregate, E::Sources>
121where
122    V: SQLParam + 'a,
123    E: Expr<'a, V>,
124    E::SQLType: BooleanLike,
125    E::Nullable: Nullability,
126{
127    let expr_sql: SQL<'a, V> = expr.into_expr_sql();
128    let needs_paren = expr_sql.chunks.len() > 1
129        || (expr_sql.chunks.len() == 1
130            && !matches!(
131                expr_sql.chunks[0],
132                SQLChunk::Raw(_) | SQLChunk::Ident(_) | SQLChunk::Number(_)
133            ));
134
135    let sql = if needs_paren {
136        SQL::from_iter([Token::NOT, Token::LPAREN])
137            .append(expr_sql)
138            .push(Token::RPAREN)
139    } else {
140        SQL::from(Token::NOT).append(expr_sql)
141    };
142    SQLExpr::new(sql)
143}
144
145// =============================================================================
146// AND
147// =============================================================================
148
149/// Logical AND of two conditions.
150///
151/// Renders `(left AND right)`. Both operands must be boolean. The result is
152/// nullable if either operand is, and is an aggregate if either operand is.
153/// `left & right` on an [`SQLExpr`] does the same. For more than two
154/// conditions, use a tuple or [`all`](super::all).
155///
156/// An [`Option`] operand that is `None` is left out, as in a condition list:
157/// `and(x, None)` renders `(x)`, and two `None`s render `TRUE`.
158///
159/// # Examples
160///
161/// ```rust
162/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
163/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
164/// # #[derive(Clone, Debug)] struct Value(String);
165/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
166/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
167/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
168/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
169/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
170/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
171/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
172/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
173/// let cond = and(users.active, gt(users.age, 18));
174/// assert_eq!(cond.sql(), r#"("users"."active" AND "users"."age" > ?)"#);
175///
176/// let name: Option<&str> = None;
177/// let cond = and(users.active, name.map(|n| eq(users.name, n)));
178/// assert_eq!(cond.sql(), r#"("users"."active")"#);
179/// ```
180#[allow(clippy::type_complexity)]
181pub fn and<'a, V, L, R>(
182    left: L,
183    right: R,
184) -> SQLExpr<
185    'a,
186    V,
187    <V::DialectMarker as DialectTypes>::Bool,
188    <L::Nullable as Nullability>::Or<R::Nullable>,
189    <L::Aggregate as AggregateKind>::Or<R::Aggregate>,
190    (L::Sources, R::Sources),
191>
192where
193    V: SQLParam + 'a,
194    L: Expr<'a, V>,
195    L::SQLType: BooleanLike,
196    R: Expr<'a, V>,
197    R::SQLType: BooleanLike,
198    R::Nullable: Nullability,
199{
200    SQLExpr::new(binary_logical_op(
201        left,
202        Token::AND,
203        right,
204        super::EMPTY_CONJUNCTION,
205    ))
206}
207
208// =============================================================================
209// OR
210// =============================================================================
211
212/// Logical OR of two conditions.
213///
214/// Renders `(left OR right)`. Both operands must be boolean. The result is
215/// nullable if either operand is, and is an aggregate if either operand is.
216/// `left | right` on an [`SQLExpr`] does the same. For more than two
217/// conditions, use [`any`](super::any).
218///
219/// An [`Option`] operand that is `None` is left out, as in a condition list:
220/// `or(x, None)` renders `(x)`, and two `None`s render `FALSE`.
221///
222/// # Examples
223///
224/// ```rust
225/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
226/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
227/// # #[derive(Clone, Debug)] struct Value(String);
228/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
229/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
230/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
231/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
232/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
233/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
234/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
235/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
236/// let cond = or(eq(users.name, "admin"), eq(users.name, "root"));
237/// assert_eq!(cond.sql(), r#"("users"."name" = ? OR "users"."name" = ?)"#);
238///
239/// let name: Option<&str> = None;
240/// let cond = or(eq(users.name, "admin"), name.map(|n| eq(users.name, n)));
241/// assert_eq!(cond.sql(), r#"("users"."name" = ?)"#);
242/// ```
243#[allow(clippy::type_complexity)]
244pub fn or<'a, V, L, R>(
245    left: L,
246    right: R,
247) -> SQLExpr<
248    'a,
249    V,
250    <V::DialectMarker as DialectTypes>::Bool,
251    <L::Nullable as Nullability>::Or<R::Nullable>,
252    <L::Aggregate as AggregateKind>::Or<R::Aggregate>,
253    (L::Sources, R::Sources),
254>
255where
256    V: SQLParam + 'a,
257    L: Expr<'a, V>,
258    L::SQLType: BooleanLike,
259    R: Expr<'a, V>,
260    R::SQLType: BooleanLike,
261    R::Nullable: Nullability,
262{
263    SQLExpr::new(binary_logical_op(
264        left,
265        Token::OR,
266        right,
267        super::EMPTY_DISJUNCTION,
268    ))
269}
270
271// =============================================================================
272// Operator Trait Implementations
273// =============================================================================
274
275/// `!expr` renders `NOT (expr)`; see [`not`].
276impl<'a, V, T, N, A, S> Not for SQLExpr<'a, V, T, N, A, S>
277where
278    V: SQLParam + 'a,
279    T: BooleanLike,
280    N: Nullability,
281    A: AggregateKind,
282{
283    type Output = SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, N, A, S>;
284
285    fn not(self) -> Self::Output {
286        not(self)
287    }
288}
289
290/// `left & right` renders `(left AND right)`; see [`and`].
291impl<'a, V, T, N, A, S, Rhs> BitAnd<Rhs> for SQLExpr<'a, V, T, N, A, S>
292where
293    V: SQLParam + 'a,
294    T: BooleanLike,
295    N: Nullability,
296    A: AggregateKind,
297    Rhs: Expr<'a, V>,
298    Rhs::SQLType: BooleanLike,
299    Rhs::Nullable: Nullability,
300{
301    type Output = SQLExpr<
302        'a,
303        V,
304        <V::DialectMarker as DialectTypes>::Bool,
305        <N as Nullability>::Or<Rhs::Nullable>,
306        <A as AggregateKind>::Or<Rhs::Aggregate>,
307        (S, Rhs::Sources),
308    >;
309
310    fn bitand(self, rhs: Rhs) -> Self::Output {
311        and(self, rhs)
312    }
313}
314
315/// `left | right` renders `(left OR right)`; see [`or`].
316impl<'a, V, T, N, A, S, Rhs> BitOr<Rhs> for SQLExpr<'a, V, T, N, A, S>
317where
318    V: SQLParam + 'a,
319    T: BooleanLike,
320    N: Nullability,
321    A: AggregateKind,
322    Rhs: Expr<'a, V>,
323    Rhs::SQLType: BooleanLike,
324    Rhs::Nullable: Nullability,
325{
326    type Output = SQLExpr<
327        'a,
328        V,
329        <V::DialectMarker as DialectTypes>::Bool,
330        <N as Nullability>::Or<Rhs::Nullable>,
331        <A as AggregateKind>::Or<Rhs::Aggregate>,
332        (S, Rhs::Sources),
333    >;
334
335    fn bitor(self, rhs: Rhs) -> Self::Output {
336        or(self, rhs)
337    }
338}