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#[inline]
43fn operand_sql<'a, V, E>(value: E) -> SQL<'a, V>
44where
45 V: SQLParam + 'a,
46 E: Expr<'a, V>,
47 E::SQLType: BooleanLike,
48{
49 value.into_expr_sql()
50}
51
52#[inline]
53fn binary_logical_op<'a, V, L, R>(left: L, token: Token, right: R) -> SQL<'a, V>
54where
55 V: SQLParam + 'a,
56 L: Expr<'a, V>,
57 L::SQLType: BooleanLike,
58 R: Expr<'a, V>,
59 R::SQLType: BooleanLike,
60{
61 SQL::from(Token::LPAREN)
62 .append(operand_sql(left))
63 .push(token)
64 .append(operand_sql(right))
65 .push(Token::RPAREN)
66}
67
68// =============================================================================
69// NOT
70// =============================================================================
71
72/// Logical negation (`NOT`).
73///
74/// Renders `NOT (expr)`; a single raw word, identifier or number is not
75/// parenthesized. The operand must be boolean. The result keeps the operand's
76/// nullability and aggregate kind. `!expr` on an [`SQLExpr`] does the same.
77///
78/// # Examples
79///
80/// ```rust
81/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
82/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
83/// # #[derive(Clone, Debug)] struct Value(String);
84/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
85/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
86/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
87/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
88/// # 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))))) }
89/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
90/// # 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> }
91/// # 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") };
92/// assert_eq!(not(users.active).sql(), r#"NOT ("users"."active")"#);
93/// assert_eq!(not(gt(users.age, 18)).sql(), r#"NOT ("users"."age" > ?)"#);
94/// ```
95///
96/// # Type safety
97///
98/// Negating a text column does not compile:
99///
100/// ```rust,compile_fail
101/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
102/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
103/// # #[derive(Clone, Debug)] struct Value(String);
104/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
105/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
106/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
107/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
108/// # 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))))) }
109/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
110/// # 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> }
111/// # 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") };
112/// let wrong = not(users.name);
113/// ```
114#[allow(clippy::type_complexity)]
115pub fn not<'a, V, E>(
116 expr: E,
117) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, E::Nullable, E::Aggregate, E::Sources>
118where
119 V: SQLParam + 'a,
120 E: Expr<'a, V>,
121 E::SQLType: BooleanLike,
122 E::Nullable: Nullability,
123{
124 let expr_sql: SQL<'a, V> = expr.into_expr_sql();
125 let needs_paren = expr_sql.chunks.len() > 1
126 || (expr_sql.chunks.len() == 1
127 && !matches!(
128 expr_sql.chunks[0],
129 SQLChunk::Raw(_) | SQLChunk::Ident(_) | SQLChunk::Number(_)
130 ));
131
132 let sql = if needs_paren {
133 SQL::from_iter([Token::NOT, Token::LPAREN])
134 .append(expr_sql)
135 .push(Token::RPAREN)
136 } else {
137 SQL::from(Token::NOT).append(expr_sql)
138 };
139 SQLExpr::new(sql)
140}
141
142// =============================================================================
143// AND
144// =============================================================================
145
146/// Logical AND of two conditions.
147///
148/// Renders `(left AND right)`. Both operands must be boolean. The result is
149/// nullable if either operand is, and is an aggregate if either operand is.
150/// `left & right` on an [`SQLExpr`] does the same. For more than two
151/// conditions, use a tuple or [`all`](super::all).
152///
153/// # Examples
154///
155/// ```rust
156/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
157/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
158/// # #[derive(Clone, Debug)] struct Value(String);
159/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
160/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
161/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
162/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
163/// # 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))))) }
164/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
165/// # 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> }
166/// # 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") };
167/// let cond = and(users.active, gt(users.age, 18));
168/// assert_eq!(cond.sql(), r#"("users"."active" AND "users"."age" > ?)"#);
169/// ```
170#[allow(clippy::type_complexity)]
171pub fn and<'a, V, L, R>(
172 left: L,
173 right: R,
174) -> SQLExpr<
175 'a,
176 V,
177 <V::DialectMarker as DialectTypes>::Bool,
178 <L::Nullable as Nullability>::Or<R::Nullable>,
179 <L::Aggregate as AggregateKind>::Or<R::Aggregate>,
180 (L::Sources, R::Sources),
181>
182where
183 V: SQLParam + 'a,
184 L: Expr<'a, V>,
185 L::SQLType: BooleanLike,
186 R: Expr<'a, V>,
187 R::SQLType: BooleanLike,
188 R::Nullable: Nullability,
189{
190 SQLExpr::new(binary_logical_op(left, Token::AND, right))
191}
192
193// =============================================================================
194// OR
195// =============================================================================
196
197/// Logical OR of two conditions.
198///
199/// Renders `(left OR right)`. Both operands must be boolean. The result is
200/// nullable if either operand is, and is an aggregate if either operand is.
201/// `left | right` on an [`SQLExpr`] does the same. For more than two
202/// conditions, use [`any`](super::any).
203///
204/// # Examples
205///
206/// ```rust
207/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
208/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
209/// # #[derive(Clone, Debug)] struct Value(String);
210/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
211/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
212/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
213/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
214/// # 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))))) }
215/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
216/// # 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> }
217/// # 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") };
218/// let cond = or(eq(users.name, "admin"), eq(users.name, "root"));
219/// assert_eq!(cond.sql(), r#"("users"."name" = ? OR "users"."name" = ?)"#);
220/// ```
221#[allow(clippy::type_complexity)]
222pub fn or<'a, V, L, R>(
223 left: L,
224 right: R,
225) -> SQLExpr<
226 'a,
227 V,
228 <V::DialectMarker as DialectTypes>::Bool,
229 <L::Nullable as Nullability>::Or<R::Nullable>,
230 <L::Aggregate as AggregateKind>::Or<R::Aggregate>,
231 (L::Sources, R::Sources),
232>
233where
234 V: SQLParam + 'a,
235 L: Expr<'a, V>,
236 L::SQLType: BooleanLike,
237 R: Expr<'a, V>,
238 R::SQLType: BooleanLike,
239 R::Nullable: Nullability,
240{
241 SQLExpr::new(binary_logical_op(left, Token::OR, right))
242}
243
244// =============================================================================
245// Operator Trait Implementations
246// =============================================================================
247
248/// `!expr` renders `NOT (expr)`; see [`not`].
249impl<'a, V, T, N, A, S> Not for SQLExpr<'a, V, T, N, A, S>
250where
251 V: SQLParam + 'a,
252 T: BooleanLike,
253 N: Nullability,
254 A: AggregateKind,
255{
256 type Output = SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, N, A, S>;
257
258 fn not(self) -> Self::Output {
259 not(self)
260 }
261}
262
263/// `left & right` renders `(left AND right)`; see [`and`].
264impl<'a, V, T, N, A, S, Rhs> BitAnd<Rhs> for SQLExpr<'a, V, T, N, A, S>
265where
266 V: SQLParam + 'a,
267 T: BooleanLike,
268 N: Nullability,
269 A: AggregateKind,
270 Rhs: Expr<'a, V>,
271 Rhs::SQLType: BooleanLike,
272 Rhs::Nullable: Nullability,
273{
274 type Output = SQLExpr<
275 'a,
276 V,
277 <V::DialectMarker as DialectTypes>::Bool,
278 <N as Nullability>::Or<Rhs::Nullable>,
279 <A as AggregateKind>::Or<Rhs::Aggregate>,
280 (S, Rhs::Sources),
281 >;
282
283 fn bitand(self, rhs: Rhs) -> Self::Output {
284 and(self, rhs)
285 }
286}
287
288/// `left | right` renders `(left OR right)`; see [`or`].
289impl<'a, V, T, N, A, S, Rhs> BitOr<Rhs> for SQLExpr<'a, V, T, N, A, S>
290where
291 V: SQLParam + 'a,
292 T: BooleanLike,
293 N: Nullability,
294 A: AggregateKind,
295 Rhs: Expr<'a, V>,
296 Rhs::SQLType: BooleanLike,
297 Rhs::Nullable: Nullability,
298{
299 type Output = SQLExpr<
300 'a,
301 V,
302 <V::DialectMarker as DialectTypes>::Bool,
303 <N as Nullability>::Or<Rhs::Nullable>,
304 <A as AggregateKind>::Or<Rhs::Aggregate>,
305 (S, Rhs::Sources),
306 >;
307
308 fn bitor(self, rhs: Rhs) -> Self::Output {
309 or(self, rhs)
310 }
311}