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}