Skip to main content

drizzle_core/expr/
cmp.rs

1//! Comparison operators: `=`, `<>`, `<`, `>`, `LIKE`, `BETWEEN`, `IS NULL`, ...
2//!
3//! Each operator is available as a function ([`eq`], [`gt`], ...) and as a
4//! method through [`ExprExt`] (`users.age.gt(18)`).
5//!
6//! # Type safety
7//!
8//! - [`eq`], [`ne`], [`gt`], [`gte`], [`lt`], [`lte`], [`between`] and the
9//!   `IS [NOT] DISTINCT FROM` functions need operands with compatible SQL
10//!   types (integers with integers, text with text, ...).
11//! - [`like`] and [`not_like`] need text on both sides.
12//! - [`is_null`], [`is_not_null`], [`is_true`] and [`is_false`] accept any
13//!   expression.
14//!
15//! Every comparison returns the dialect's boolean type. Its `Nullable` is
16//! always [`NonNull`], but its [sources](ExprSources) record each operand's
17//! nullability, so a comparison with a nullable or outer-joined operand
18//! decodes as nullable when selected (`NULL = 1` is NULL in SQL). The
19//! `IS ...` tests ([`is_null`], [`is_distinct_from`], [`is_true`], ...) are
20//! never NULL.
21
22use crate::dialect::{Dialect, DialectTypes};
23use crate::sql::{SQL, Token};
24use crate::traits::SQLParam;
25use crate::types::{Compatible, DataType, Textual};
26
27use super::{AggregateKind, Expr, ExprSources, NonNull, Nullability, SQLExpr};
28use crate::scope::{Arg, ScopeOnly};
29
30/// Sources of a NULL-propagating comparison: NULL when either operand is.
31type CmpSources<'a, V, L, R> = (
32    Arg<<L as Expr<'a, V>>::Nullable, <L as ExprSources>::Sources>,
33    Arg<<R as ComparisonOperand<'a, V, L>>::Nullable, <R as ComparisonOperand<'a, V, L>>::Sources>,
34);
35
36/// Sources of `BETWEEN`: NULL when any operand is.
37type BetweenSources<'a, V, E, L, H> = (
38    Arg<<E as Expr<'a, V>>::Nullable, <E as ExprSources>::Sources>,
39    (
40        Arg<
41            <L as ComparisonOperand<'a, V, E>>::Nullable,
42            <L as ComparisonOperand<'a, V, E>>::Sources,
43        >,
44        Arg<
45            <H as ComparisonOperand<'a, V, E>>::Nullable,
46            <H as ComparisonOperand<'a, V, E>>::Sources,
47        >,
48    ),
49);
50
51/// Sources of a NULL-safe comparison: never NULL.
52type NullSafeCmpSources<'a, V, L, R> = ScopeOnly<(
53    <L as ExprSources>::Sources,
54    <R as ComparisonOperand<'a, V, L>>::Sources,
55)>;
56
57// =============================================================================
58// Internal Helper
59// =============================================================================
60
61fn binary_op<'a, V, L, R>(left: L, operator: Token, right: R) -> SQL<'a, V>
62where
63    V: SQLParam + 'a,
64    L: Expr<'a, V>,
65    R: ComparisonOperand<'a, V, L>,
66    L::SQLType: Compatible<<R as ComparisonOperand<'a, V, L>>::SQLType>,
67{
68    let left_sql = operand_sql(left);
69    let right_sql = ComparisonOperand::into_comparison_sql(right);
70
71    left_sql.push(operator).append(right_sql)
72}
73
74#[inline]
75fn operand_sql<'a, V, T>(value: T) -> SQL<'a, V>
76where
77    V: SQLParam + 'a,
78    T: Expr<'a, V>,
79{
80    value.into_expr_sql()
81}
82
83/// A value that can be the right-hand side of a comparison with `L`.
84///
85/// Every [`Expr`] whose SQL type is compatible with `L`'s is an operand. The
86/// table macros also implement this trait for a column's custom Rust type
87/// (such as an enum or JSON struct) against that one column, so
88/// `eq(table.custom, value)` works without making the custom type an
89/// expression everywhere.
90pub trait ComparisonOperand<'a, V, L>: Sized
91where
92    V: SQLParam + 'a,
93    L: Expr<'a, V>,
94{
95    /// SQL type of the operand.
96    type SQLType: DataType;
97    /// Whether the operand can be NULL.
98    type Nullable: Nullability;
99    /// Whether the operand is an aggregate.
100    type Aggregate: AggregateKind;
101    /// Tables the operand reads; see [`ExprSources::Sources`].
102    type Sources;
103
104    /// Renders the operand.
105    fn into_comparison_sql(self) -> SQL<'a, V>;
106}
107
108impl<'a, V, L, R> ComparisonOperand<'a, V, L> for R
109where
110    V: SQLParam + 'a,
111    L: Expr<'a, V>,
112    R: Expr<'a, V>,
113    L::SQLType: Compatible<R::SQLType>,
114{
115    type SQLType = R::SQLType;
116    type Nullable = R::Nullable;
117    type Aggregate = R::Aggregate;
118    type Sources = R::Sources;
119
120    fn into_comparison_sql(self) -> SQL<'a, V> {
121        self.into_expr_sql()
122    }
123}
124
125// =============================================================================
126// Equality Comparisons
127// =============================================================================
128
129/// Equality comparison (`=`).
130///
131/// Renders `left = right`. Both sides must have compatible SQL types. The
132/// result is the dialect's boolean. It is NULL when either side is, and an
133/// aggregate when either side is.
134///
135/// # Examples
136///
137/// ```rust
138/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
139/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
140/// # #[derive(Clone, Debug)] struct Value(String);
141/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
142/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
143/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
144/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
145/// # 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))))) }
146/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
147/// # 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> }
148/// # 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") };
149/// let by_id = eq(users.id, 42);
150/// assert_eq!(by_id.sql(), r#""users"."id" = ?"#);
151/// ```
152///
153/// # Type safety
154///
155/// Comparing an integer column with text does not compile:
156///
157/// ```rust,compile_fail
158/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
159/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
160/// # #[derive(Clone, Debug)] struct Value(String);
161/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
162/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
163/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
164/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
165/// # 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))))) }
166/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
167/// # 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> }
168/// # 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") };
169/// let wrong = eq(users.id, "hello");
170/// ```
171#[allow(clippy::type_complexity)]
172pub fn eq<'a, V, L, R>(
173    left: L,
174    right: R,
175) -> SQLExpr<
176    'a,
177    V,
178    <V::DialectMarker as DialectTypes>::Bool,
179    NonNull,
180    <L::Aggregate as AggregateKind>::Or<<R as ComparisonOperand<'a, V, L>>::Aggregate>,
181    CmpSources<'a, V, L, R>,
182>
183where
184    V: SQLParam + 'a,
185    L: Expr<'a, V>,
186    R: ComparisonOperand<'a, V, L>,
187    L::SQLType: Compatible<<R as ComparisonOperand<'a, V, L>>::SQLType>,
188{
189    SQLExpr::new(binary_op(left, Token::EQ, right))
190}
191
192/// Inequality comparison (`<>`).
193///
194/// Renders `left <> right`. Both sides must have compatible SQL types. The
195/// result is the dialect's boolean. It is NULL when either side is, and an
196/// aggregate when either side is.
197///
198/// # Examples
199///
200/// ```rust
201/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
202/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
203/// # #[derive(Clone, Debug)] struct Value(String);
204/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
205/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
206/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
207/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
208/// # 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))))) }
209/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
210/// # 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> }
211/// # 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") };
212/// assert_eq!(ne(users.name, "admin").sql(), r#""users"."name" <> ?"#);
213/// ```
214#[allow(clippy::type_complexity)]
215pub fn ne<'a, V, L, R>(
216    left: L,
217    right: R,
218) -> SQLExpr<
219    'a,
220    V,
221    <V::DialectMarker as DialectTypes>::Bool,
222    NonNull,
223    <L::Aggregate as AggregateKind>::Or<<R as ComparisonOperand<'a, V, L>>::Aggregate>,
224    CmpSources<'a, V, L, R>,
225>
226where
227    V: SQLParam + 'a,
228    L: Expr<'a, V>,
229    R: ComparisonOperand<'a, V, L>,
230    L::SQLType: Compatible<<R as ComparisonOperand<'a, V, L>>::SQLType>,
231{
232    SQLExpr::new(binary_op(left, Token::NE, right))
233}
234
235/// Inequality comparison (`<>`); same as [`ne`].
236#[allow(clippy::type_complexity)]
237pub fn neq<'a, V, L, R>(
238    left: L,
239    right: R,
240) -> SQLExpr<
241    'a,
242    V,
243    <V::DialectMarker as DialectTypes>::Bool,
244    NonNull,
245    <L::Aggregate as AggregateKind>::Or<<R as ComparisonOperand<'a, V, L>>::Aggregate>,
246    CmpSources<'a, V, L, R>,
247>
248where
249    V: SQLParam + 'a,
250    L: Expr<'a, V>,
251    R: ComparisonOperand<'a, V, L>,
252    L::SQLType: Compatible<<R as ComparisonOperand<'a, V, L>>::SQLType>,
253{
254    ne(left, right)
255}
256
257// =============================================================================
258// Ordering Comparisons
259// =============================================================================
260
261/// Greater-than comparison (`>`).
262///
263/// Renders `left > right`. Both sides must have compatible SQL types. The
264/// result is the dialect's boolean. It is NULL when either side is, and an
265/// aggregate when either side is.
266///
267/// # Examples
268///
269/// ```rust
270/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
271/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
272/// # #[derive(Clone, Debug)] struct Value(String);
273/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
274/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
275/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
276/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
277/// # 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))))) }
278/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
279/// # 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> }
280/// # 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") };
281/// assert_eq!(gt(users.age, 18).sql(), r#""users"."age" > ?"#);
282/// ```
283#[allow(clippy::type_complexity)]
284pub fn gt<'a, V, L, R>(
285    left: L,
286    right: R,
287) -> SQLExpr<
288    'a,
289    V,
290    <V::DialectMarker as DialectTypes>::Bool,
291    NonNull,
292    <L::Aggregate as AggregateKind>::Or<<R as ComparisonOperand<'a, V, L>>::Aggregate>,
293    CmpSources<'a, V, L, R>,
294>
295where
296    V: SQLParam + 'a,
297    L: Expr<'a, V>,
298    R: ComparisonOperand<'a, V, L>,
299    L::SQLType: Compatible<<R as ComparisonOperand<'a, V, L>>::SQLType>,
300{
301    SQLExpr::new(binary_op(left, Token::GT, right))
302}
303
304/// Greater-than-or-equal comparison (`>=`).
305///
306/// Renders `left >= right`. Both sides must have compatible SQL types. The
307/// result is the dialect's boolean. It is NULL when either side is, and an
308/// aggregate when either side is.
309///
310/// # Examples
311///
312/// ```rust
313/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
314/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
315/// # #[derive(Clone, Debug)] struct Value(String);
316/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
317/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
318/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
319/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
320/// # 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))))) }
321/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
322/// # 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> }
323/// # 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") };
324/// assert_eq!(gte(users.age, 18).sql(), r#""users"."age" >= ?"#);
325/// ```
326#[allow(clippy::type_complexity)]
327pub fn gte<'a, V, L, R>(
328    left: L,
329    right: R,
330) -> SQLExpr<
331    'a,
332    V,
333    <V::DialectMarker as DialectTypes>::Bool,
334    NonNull,
335    <L::Aggregate as AggregateKind>::Or<<R as ComparisonOperand<'a, V, L>>::Aggregate>,
336    CmpSources<'a, V, L, R>,
337>
338where
339    V: SQLParam + 'a,
340    L: Expr<'a, V>,
341    R: ComparisonOperand<'a, V, L>,
342    L::SQLType: Compatible<<R as ComparisonOperand<'a, V, L>>::SQLType>,
343{
344    SQLExpr::new(binary_op(left, Token::GE, right))
345}
346
347/// Less-than comparison (`<`).
348///
349/// Renders `left < right`. Both sides must have compatible SQL types. The
350/// result is the dialect's boolean. It is NULL when either side is, and an
351/// aggregate when either side is.
352///
353/// # Examples
354///
355/// ```rust
356/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
357/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
358/// # #[derive(Clone, Debug)] struct Value(String);
359/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
360/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
361/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
362/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
363/// # 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))))) }
364/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
365/// # 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> }
366/// # 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") };
367/// assert_eq!(lt(users.age, 65).sql(), r#""users"."age" < ?"#);
368/// ```
369#[allow(clippy::type_complexity)]
370pub fn lt<'a, V, L, R>(
371    left: L,
372    right: R,
373) -> SQLExpr<
374    'a,
375    V,
376    <V::DialectMarker as DialectTypes>::Bool,
377    NonNull,
378    <L::Aggregate as AggregateKind>::Or<<R as ComparisonOperand<'a, V, L>>::Aggregate>,
379    CmpSources<'a, V, L, R>,
380>
381where
382    V: SQLParam + 'a,
383    L: Expr<'a, V>,
384    R: ComparisonOperand<'a, V, L>,
385    L::SQLType: Compatible<<R as ComparisonOperand<'a, V, L>>::SQLType>,
386{
387    SQLExpr::new(binary_op(left, Token::LT, right))
388}
389
390/// Less-than-or-equal comparison (`<=`).
391///
392/// Renders `left <= right`. Both sides must have compatible SQL types. The
393/// result is the dialect's boolean. It is NULL when either side is, and an
394/// aggregate when either side is.
395///
396/// # Examples
397///
398/// ```rust
399/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
400/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
401/// # #[derive(Clone, Debug)] struct Value(String);
402/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
403/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
404/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
405/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
406/// # 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))))) }
407/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
408/// # 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> }
409/// # 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") };
410/// assert_eq!(lte(users.age, 65).sql(), r#""users"."age" <= ?"#);
411/// ```
412#[allow(clippy::type_complexity)]
413pub fn lte<'a, V, L, R>(
414    left: L,
415    right: R,
416) -> SQLExpr<
417    'a,
418    V,
419    <V::DialectMarker as DialectTypes>::Bool,
420    NonNull,
421    <L::Aggregate as AggregateKind>::Or<<R as ComparisonOperand<'a, V, L>>::Aggregate>,
422    CmpSources<'a, V, L, R>,
423>
424where
425    V: SQLParam + 'a,
426    L: Expr<'a, V>,
427    R: ComparisonOperand<'a, V, L>,
428    L::SQLType: Compatible<<R as ComparisonOperand<'a, V, L>>::SQLType>,
429{
430    SQLExpr::new(binary_op(left, Token::LE, right))
431}
432
433// =============================================================================
434// Pattern Matching
435// =============================================================================
436
437/// Pattern match (`LIKE`).
438///
439/// Renders `left LIKE pattern`. Both sides must be text. In the pattern, `%`
440/// matches any run of characters and `_` matches one character. The result is
441/// the dialect's boolean, NULL when either side is. Case sensitivity follows
442/// the database: SQLite and MySQL (with its default collations) ignore case,
443/// PostgreSQL does not.
444///
445/// # Examples
446///
447/// ```rust
448/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
449/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
450/// # #[derive(Clone, Debug)] struct Value(String);
451/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
452/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
453/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
454/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
455/// # 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))))) }
456/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
457/// # 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> }
458/// # 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") };
459/// let starts_with_a = like(users.name, "A%");
460/// assert_eq!(starts_with_a.sql(), r#""users"."name" LIKE ?"#);
461/// ```
462///
463/// # Type safety
464///
465/// `LIKE` on an integer column does not compile:
466///
467/// ```rust,compile_fail
468/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
469/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
470/// # #[derive(Clone, Debug)] struct Value(String);
471/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
472/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
473/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
474/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
475/// # 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))))) }
476/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
477/// # 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> }
478/// # 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") };
479/// let wrong = like(users.id, "1%");
480/// ```
481#[allow(clippy::type_complexity)]
482pub fn like<'a, V, L, R>(
483    left: L,
484    pattern: R,
485) -> SQLExpr<
486    'a,
487    V,
488    <V::DialectMarker as DialectTypes>::Bool,
489    NonNull,
490    <L::Aggregate as AggregateKind>::Or<<R as ComparisonOperand<'a, V, L>>::Aggregate>,
491    CmpSources<'a, V, L, R>,
492>
493where
494    V: SQLParam + 'a,
495    L: Expr<'a, V>,
496    R: ComparisonOperand<'a, V, L>,
497    L::SQLType: Compatible<<R as ComparisonOperand<'a, V, L>>::SQLType>,
498    L::SQLType: Textual,
499    <R as ComparisonOperand<'a, V, L>>::SQLType: Textual,
500{
501    SQLExpr::new(
502        operand_sql(left)
503            .push(Token::LIKE)
504            .append(ComparisonOperand::into_comparison_sql(pattern)),
505    )
506}
507
508/// Negated pattern match (`NOT LIKE`).
509///
510/// Renders `left NOT LIKE pattern`. Both sides must be text. See [`like`].
511///
512/// # Examples
513///
514/// ```rust
515/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
516/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
517/// # #[derive(Clone, Debug)] struct Value(String);
518/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
519/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
520/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
521/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
522/// # 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))))) }
523/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
524/// # 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> }
525/// # 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") };
526/// assert_eq!(not_like(users.name, "%bot%").sql(), r#""users"."name" NOT LIKE ?"#);
527/// ```
528#[allow(clippy::type_complexity)]
529pub fn not_like<'a, V, L, R>(
530    left: L,
531    pattern: R,
532) -> SQLExpr<
533    'a,
534    V,
535    <V::DialectMarker as DialectTypes>::Bool,
536    NonNull,
537    <L::Aggregate as AggregateKind>::Or<<R as ComparisonOperand<'a, V, L>>::Aggregate>,
538    CmpSources<'a, V, L, R>,
539>
540where
541    V: SQLParam + 'a,
542    L: Expr<'a, V>,
543    R: ComparisonOperand<'a, V, L>,
544    L::SQLType: Compatible<<R as ComparisonOperand<'a, V, L>>::SQLType>,
545    L::SQLType: Textual,
546    <R as ComparisonOperand<'a, V, L>>::SQLType: Textual,
547{
548    SQLExpr::new(
549        operand_sql(left)
550            .push(Token::NOT)
551            .push(Token::LIKE)
552            .append(ComparisonOperand::into_comparison_sql(pattern)),
553    )
554}
555
556// =============================================================================
557// Range Comparisons
558// =============================================================================
559
560/// Range check (`BETWEEN`), inclusive on both ends.
561///
562/// Renders `(expr BETWEEN low AND high)`. Both bounds must have a SQL type
563/// compatible with `expr`. The result is the dialect's boolean, NULL when
564/// any operand is.
565///
566/// # Examples
567///
568/// ```rust
569/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
570/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
571/// # #[derive(Clone, Debug)] struct Value(String);
572/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
573/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
574/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
575/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
576/// # 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))))) }
577/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
578/// # 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> }
579/// # 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") };
580/// let working_age = between(users.age, 18, 65);
581/// assert_eq!(working_age.sql(), r#"("users"."age" BETWEEN ? AND ?)"#);
582/// ```
583#[allow(clippy::type_complexity)]
584pub fn between<'a, V, E, L, H>(
585    expr: E,
586    low: L,
587    high: H,
588) -> SQLExpr<
589    'a,
590    V,
591    <V::DialectMarker as DialectTypes>::Bool,
592    NonNull,
593    <<E::Aggregate as AggregateKind>::Or<<L as ComparisonOperand<'a, V, E>>::Aggregate> as AggregateKind>::Or<<H as ComparisonOperand<'a, V, E>>::Aggregate,>,
594    BetweenSources<'a, V, E, L, H>,
595>
596where
597    V: SQLParam + 'a,
598    E: Expr<'a, V>,
599    L: ComparisonOperand<'a, V, E>,
600    H: ComparisonOperand<'a, V, E>,
601    E::SQLType: Compatible<<L as ComparisonOperand<'a, V, E>>::SQLType>,
602    E::SQLType: Compatible<<H as ComparisonOperand<'a, V, E>>::SQLType>,
603{
604    SQLExpr::new(
605        SQL::from(Token::LPAREN)
606            .append(operand_sql(expr))
607            .push(Token::BETWEEN)
608            .append(ComparisonOperand::into_comparison_sql(low))
609            .push(Token::AND)
610            .append(ComparisonOperand::into_comparison_sql(high))
611            .push(Token::RPAREN),
612    )
613}
614
615/// Negated range check (`NOT BETWEEN`).
616///
617/// Renders `(expr NOT BETWEEN low AND high)`. See [`between`].
618///
619/// # Examples
620///
621/// ```rust
622/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
623/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
624/// # #[derive(Clone, Debug)] struct Value(String);
625/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
626/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
627/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
628/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
629/// # 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))))) }
630/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
631/// # 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> }
632/// # 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") };
633/// let minors = not_between(users.age, 18, 120);
634/// assert_eq!(minors.sql(), r#"("users"."age" NOT BETWEEN ? AND ?)"#);
635/// ```
636#[allow(clippy::type_complexity)]
637pub fn not_between<'a, V, E, L, H>(
638    expr: E,
639    low: L,
640    high: H,
641) -> SQLExpr<
642    'a,
643    V,
644    <V::DialectMarker as DialectTypes>::Bool,
645    NonNull,
646    <<E::Aggregate as AggregateKind>::Or<<L as ComparisonOperand<'a, V, E>>::Aggregate> as AggregateKind>::Or<<H as ComparisonOperand<'a, V, E>>::Aggregate,>,
647    BetweenSources<'a, V, E, L, H>,
648>
649where
650    V: SQLParam + 'a,
651    E: Expr<'a, V>,
652    L: ComparisonOperand<'a, V, E>,
653    H: ComparisonOperand<'a, V, E>,
654    E::SQLType: Compatible<<L as ComparisonOperand<'a, V, E>>::SQLType>,
655    E::SQLType: Compatible<<H as ComparisonOperand<'a, V, E>>::SQLType>,
656{
657    SQLExpr::new(
658        SQL::from(Token::LPAREN)
659            .append(operand_sql(expr))
660            .push(Token::NOT)
661            .push(Token::BETWEEN)
662            .append(ComparisonOperand::into_comparison_sql(low))
663            .push(Token::AND)
664            .append(ComparisonOperand::into_comparison_sql(high))
665            .push(Token::RPAREN),
666    )
667}
668
669// =============================================================================
670// NULL Checks
671// =============================================================================
672
673/// NULL check (`IS NULL`).
674///
675/// Renders `expr IS NULL`. Accepts any expression. The result is the
676/// dialect's boolean and is never NULL.
677///
678/// # Examples
679///
680/// ```rust
681/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
682/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
683/// # #[derive(Clone, Debug)] struct Value(String);
684/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
685/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
686/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
687/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
688/// # 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))))) }
689/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
690/// # 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> }
691/// # 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") };
692/// assert_eq!(is_null(users.email).sql(), r#""users"."email" IS NULL"#);
693/// ```
694#[allow(clippy::type_complexity)]
695pub fn is_null<'a, V, E>(
696    expr: E,
697) -> SQLExpr<
698    'a,
699    V,
700    <V::DialectMarker as DialectTypes>::Bool,
701    NonNull,
702    E::Aggregate,
703    ScopeOnly<E::Sources>,
704>
705where
706    V: SQLParam + 'a,
707    E: Expr<'a, V>,
708{
709    SQLExpr::new(operand_sql(expr).push(Token::IS).push(Token::NULL))
710}
711
712/// Non-NULL check (`IS NOT NULL`).
713///
714/// Renders `expr IS NOT NULL`. Accepts any expression. The result is the
715/// dialect's boolean and is never NULL.
716///
717/// # Examples
718///
719/// ```rust
720/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
721/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
722/// # #[derive(Clone, Debug)] struct Value(String);
723/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
724/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
725/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
726/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
727/// # 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))))) }
728/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
729/// # 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> }
730/// # 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") };
731/// assert_eq!(is_not_null(users.email).sql(), r#""users"."email" IS NOT NULL"#);
732/// ```
733#[allow(clippy::type_complexity)]
734pub fn is_not_null<'a, V, E>(
735    expr: E,
736) -> SQLExpr<
737    'a,
738    V,
739    <V::DialectMarker as DialectTypes>::Bool,
740    NonNull,
741    E::Aggregate,
742    ScopeOnly<E::Sources>,
743>
744where
745    V: SQLParam + 'a,
746    E: Expr<'a, V>,
747{
748    SQLExpr::new(
749        operand_sql(expr)
750            .push(Token::IS)
751            .push(Token::NOT)
752            .push(Token::NULL),
753    )
754}
755
756// =============================================================================
757// IS DISTINCT FROM
758// =============================================================================
759
760/// NULL-safe inequality (`IS DISTINCT FROM`).
761///
762/// Like `<>`, but NULL is compared as an ordinary value, so the result is
763/// never NULL:
764///
765/// - `NULL IS DISTINCT FROM NULL` is false;
766/// - `NULL IS DISTINCT FROM 5` is true.
767///
768/// SQLite and PostgreSQL render `left IS DISTINCT FROM right`; MySQL renders
769/// `NOT (left <=> right)`. Both sides must have compatible SQL types.
770///
771/// # Examples
772///
773/// ```rust
774/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
775/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
776/// # #[derive(Clone, Debug)] struct Value(String);
777/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
778/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
779/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
780/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
781/// # 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))))) }
782/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
783/// # 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> }
784/// # 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") };
785/// let changed = is_distinct_from(users.email, "old@example.com");
786/// assert_eq!(changed.sql(), r#""users"."email" IS DISTINCT FROM ?"#);
787/// ```
788#[allow(clippy::type_complexity)]
789pub fn is_distinct_from<'a, V, L, R>(
790    left: L,
791    right: R,
792) -> SQLExpr<
793    'a,
794    V,
795    <V::DialectMarker as DialectTypes>::Bool,
796    NonNull,
797    <L::Aggregate as AggregateKind>::Or<<R as ComparisonOperand<'a, V, L>>::Aggregate>,
798    NullSafeCmpSources<'a, V, L, R>,
799>
800where
801    V: SQLParam + 'a,
802    L: Expr<'a, V>,
803    R: ComparisonOperand<'a, V, L>,
804    L::SQLType: Compatible<<R as ComparisonOperand<'a, V, L>>::SQLType>,
805{
806    let left = operand_sql(left);
807    let right = ComparisonOperand::into_comparison_sql(right);
808    let sql = match V::DIALECT {
809        Dialect::MySQL => SQL::from(Token::NOT)
810            .push(Token::LPAREN)
811            .append(left)
812            .append(SQL::raw("<=>"))
813            .append(right)
814            .push(Token::RPAREN),
815        Dialect::SQLite | Dialect::PostgreSQL => left
816            .push(Token::IS)
817            .push(Token::DISTINCT)
818            .push(Token::FROM)
819            .append(right),
820    };
821    SQLExpr::new(sql)
822}
823
824/// NULL-safe equality (`IS NOT DISTINCT FROM`).
825///
826/// Like `=`, but NULL is compared as an ordinary value, so the result is
827/// never NULL:
828///
829/// - `NULL IS NOT DISTINCT FROM NULL` is true;
830/// - `NULL IS NOT DISTINCT FROM 5` is false.
831///
832/// SQLite and PostgreSQL render `left IS NOT DISTINCT FROM right`; MySQL
833/// renders `left <=> right`. Both sides must have compatible SQL types.
834///
835/// # Examples
836///
837/// ```rust
838/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
839/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
840/// # #[derive(Clone, Debug)] struct Value(String);
841/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
842/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
843/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
844/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
845/// # 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))))) }
846/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
847/// # 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> }
848/// # 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") };
849/// let same = is_not_distinct_from(users.email, "a@example.com");
850/// assert_eq!(same.sql(), r#""users"."email" IS NOT DISTINCT FROM ?"#);
851/// ```
852#[allow(clippy::type_complexity)]
853pub fn is_not_distinct_from<'a, V, L, R>(
854    left: L,
855    right: R,
856) -> SQLExpr<
857    'a,
858    V,
859    <V::DialectMarker as DialectTypes>::Bool,
860    NonNull,
861    <L::Aggregate as AggregateKind>::Or<<R as ComparisonOperand<'a, V, L>>::Aggregate>,
862    NullSafeCmpSources<'a, V, L, R>,
863>
864where
865    V: SQLParam + 'a,
866    L: Expr<'a, V>,
867    R: ComparisonOperand<'a, V, L>,
868    L::SQLType: Compatible<<R as ComparisonOperand<'a, V, L>>::SQLType>,
869{
870    let left = operand_sql(left);
871    let right = ComparisonOperand::into_comparison_sql(right);
872    let sql = match V::DIALECT {
873        Dialect::MySQL => left.append(SQL::raw("<=>")).append(right),
874        Dialect::SQLite | Dialect::PostgreSQL => left
875            .push(Token::IS)
876            .push(Token::NOT)
877            .push(Token::DISTINCT)
878            .push(Token::FROM)
879            .append(right),
880    };
881    SQLExpr::new(sql)
882}
883
884// =============================================================================
885// Boolean Testing
886// =============================================================================
887
888/// Truth test (`IS TRUE`) that never returns NULL.
889///
890/// Renders `expr IS TRUE`. Unlike `= TRUE`, a NULL input gives false instead
891/// of NULL.
892///
893/// # Examples
894///
895/// ```rust
896/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
897/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
898/// # #[derive(Clone, Debug)] struct Value(String);
899/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
900/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
901/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
902/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
903/// # 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))))) }
904/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
905/// # 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> }
906/// # 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") };
907/// assert_eq!(is_true(users.active).sql(), r#""users"."active" IS TRUE"#);
908/// ```
909#[allow(clippy::type_complexity)]
910pub fn is_true<'a, V, E>(
911    expr: E,
912) -> SQLExpr<
913    'a,
914    V,
915    <V::DialectMarker as DialectTypes>::Bool,
916    NonNull,
917    E::Aggregate,
918    ScopeOnly<E::Sources>,
919>
920where
921    V: SQLParam + 'a,
922    E: Expr<'a, V>,
923{
924    SQLExpr::new(operand_sql(expr).push(Token::IS).append(SQL::raw("TRUE")))
925}
926
927/// Falsity test (`IS FALSE`) that never returns NULL.
928///
929/// Renders `expr IS FALSE`. Unlike `= FALSE`, a NULL input gives false
930/// instead of NULL.
931///
932/// # Examples
933///
934/// ```rust
935/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
936/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
937/// # #[derive(Clone, Debug)] struct Value(String);
938/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
939/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
940/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
941/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
942/// # 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))))) }
943/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
944/// # 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> }
945/// # 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") };
946/// assert_eq!(is_false(users.active).sql(), r#""users"."active" IS FALSE"#);
947/// ```
948#[allow(clippy::type_complexity)]
949pub fn is_false<'a, V, E>(
950    expr: E,
951) -> SQLExpr<
952    'a,
953    V,
954    <V::DialectMarker as DialectTypes>::Bool,
955    NonNull,
956    E::Aggregate,
957    ScopeOnly<E::Sources>,
958>
959where
960    V: SQLParam + 'a,
961    E: Expr<'a, V>,
962{
963    SQLExpr::new(operand_sql(expr).push(Token::IS).append(SQL::raw("FALSE")))
964}
965
966// =============================================================================
967// Method-based Comparison API (Extension Trait)
968// =============================================================================
969
970/// Method syntax for comparisons: `users.age.gt(18)` instead of `gt(users.age, 18)`.
971///
972/// Implemented for every [`Expr`]. Each method calls the function of the same
973/// name in this module (`ge` and `le` call [`gte`] and [`lte`]) and has the
974/// same type checks.
975///
976/// # Examples
977///
978/// ```rust
979/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
980/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
981/// # #[derive(Clone, Debug)] struct Value(String);
982/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
983/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
984/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
985/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
986/// # 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))))) }
987/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
988/// # 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> }
989/// # 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") };
990/// let filter = users.age.ge(18) & users.email.is_not_null();
991/// assert_eq!(
992///     filter.sql(),
993///     r#"("users"."age" >= ? AND "users"."email" IS NOT NULL)"#
994/// );
995/// ```
996pub trait ExprExt<'a, V: SQLParam>: Expr<'a, V> + Sized {
997    /// Equality comparison (`=`); see [`eq`].
998    #[allow(clippy::type_complexity)]
999    fn eq<R>(
1000        self,
1001        other: R,
1002    ) -> SQLExpr<
1003        'a,
1004        V,
1005        <V::DialectMarker as DialectTypes>::Bool,
1006        NonNull,
1007        <Self::Aggregate as AggregateKind>::Or<<R as ComparisonOperand<'a, V, Self>>::Aggregate>,
1008        CmpSources<'a, V, Self, R>,
1009    >
1010    where
1011        R: ComparisonOperand<'a, V, Self>,
1012        Self::SQLType: Compatible<<R as ComparisonOperand<'a, V, Self>>::SQLType>,
1013    {
1014        eq(self, other)
1015    }
1016
1017    /// Inequality comparison (`<>`); see [`ne`].
1018    #[allow(clippy::type_complexity)]
1019    fn ne<R>(
1020        self,
1021        other: R,
1022    ) -> SQLExpr<
1023        'a,
1024        V,
1025        <V::DialectMarker as DialectTypes>::Bool,
1026        NonNull,
1027        <Self::Aggregate as AggregateKind>::Or<<R as ComparisonOperand<'a, V, Self>>::Aggregate>,
1028        CmpSources<'a, V, Self, R>,
1029    >
1030    where
1031        R: ComparisonOperand<'a, V, Self>,
1032        Self::SQLType: Compatible<<R as ComparisonOperand<'a, V, Self>>::SQLType>,
1033    {
1034        ne(self, other)
1035    }
1036
1037    /// Greater-than comparison (`>`); see [`gt`].
1038    #[allow(clippy::type_complexity)]
1039    fn gt<R>(
1040        self,
1041        other: R,
1042    ) -> SQLExpr<
1043        'a,
1044        V,
1045        <V::DialectMarker as DialectTypes>::Bool,
1046        NonNull,
1047        <Self::Aggregate as AggregateKind>::Or<<R as ComparisonOperand<'a, V, Self>>::Aggregate>,
1048        CmpSources<'a, V, Self, R>,
1049    >
1050    where
1051        R: ComparisonOperand<'a, V, Self>,
1052        Self::SQLType: Compatible<<R as ComparisonOperand<'a, V, Self>>::SQLType>,
1053    {
1054        gt(self, other)
1055    }
1056
1057    /// Greater-than-or-equal comparison (`>=`); see [`gte`].
1058    #[allow(clippy::type_complexity)]
1059    fn ge<R>(
1060        self,
1061        other: R,
1062    ) -> SQLExpr<
1063        'a,
1064        V,
1065        <V::DialectMarker as DialectTypes>::Bool,
1066        NonNull,
1067        <Self::Aggregate as AggregateKind>::Or<<R as ComparisonOperand<'a, V, Self>>::Aggregate>,
1068        CmpSources<'a, V, Self, R>,
1069    >
1070    where
1071        R: ComparisonOperand<'a, V, Self>,
1072        Self::SQLType: Compatible<<R as ComparisonOperand<'a, V, Self>>::SQLType>,
1073    {
1074        gte(self, other)
1075    }
1076
1077    /// Less-than comparison (`<`); see [`lt`].
1078    #[allow(clippy::type_complexity)]
1079    fn lt<R>(
1080        self,
1081        other: R,
1082    ) -> SQLExpr<
1083        'a,
1084        V,
1085        <V::DialectMarker as DialectTypes>::Bool,
1086        NonNull,
1087        <Self::Aggregate as AggregateKind>::Or<<R as ComparisonOperand<'a, V, Self>>::Aggregate>,
1088        CmpSources<'a, V, Self, R>,
1089    >
1090    where
1091        R: ComparisonOperand<'a, V, Self>,
1092        Self::SQLType: Compatible<<R as ComparisonOperand<'a, V, Self>>::SQLType>,
1093    {
1094        lt(self, other)
1095    }
1096
1097    /// Less-than-or-equal comparison (`<=`); see [`lte`].
1098    #[allow(clippy::type_complexity)]
1099    fn le<R>(
1100        self,
1101        other: R,
1102    ) -> SQLExpr<
1103        'a,
1104        V,
1105        <V::DialectMarker as DialectTypes>::Bool,
1106        NonNull,
1107        <Self::Aggregate as AggregateKind>::Or<<R as ComparisonOperand<'a, V, Self>>::Aggregate>,
1108        CmpSources<'a, V, Self, R>,
1109    >
1110    where
1111        R: ComparisonOperand<'a, V, Self>,
1112        Self::SQLType: Compatible<<R as ComparisonOperand<'a, V, Self>>::SQLType>,
1113    {
1114        lte(self, other)
1115    }
1116
1117    /// Pattern match (`LIKE`); see [`like`].
1118    #[allow(clippy::type_complexity)]
1119    fn like<R>(
1120        self,
1121        pattern: R,
1122    ) -> SQLExpr<
1123        'a,
1124        V,
1125        <V::DialectMarker as DialectTypes>::Bool,
1126        NonNull,
1127        <Self::Aggregate as AggregateKind>::Or<<R as ComparisonOperand<'a, V, Self>>::Aggregate>,
1128        CmpSources<'a, V, Self, R>,
1129    >
1130    where
1131        R: ComparisonOperand<'a, V, Self>,
1132        Self::SQLType: Compatible<<R as ComparisonOperand<'a, V, Self>>::SQLType>,
1133        Self::SQLType: Textual,
1134        <R as ComparisonOperand<'a, V, Self>>::SQLType: Textual,
1135    {
1136        like(self, pattern)
1137    }
1138
1139    /// Negated pattern match (`NOT LIKE`); see [`not_like`].
1140    #[allow(clippy::type_complexity)]
1141    fn not_like<R>(
1142        self,
1143        pattern: R,
1144    ) -> SQLExpr<
1145        'a,
1146        V,
1147        <V::DialectMarker as DialectTypes>::Bool,
1148        NonNull,
1149        <Self::Aggregate as AggregateKind>::Or<<R as ComparisonOperand<'a, V, Self>>::Aggregate>,
1150        CmpSources<'a, V, Self, R>,
1151    >
1152    where
1153        R: ComparisonOperand<'a, V, Self>,
1154        Self::SQLType: Compatible<<R as ComparisonOperand<'a, V, Self>>::SQLType>,
1155        Self::SQLType: Textual,
1156        <R as ComparisonOperand<'a, V, Self>>::SQLType: Textual,
1157    {
1158        not_like(self, pattern)
1159    }
1160
1161    /// NULL check (`IS NULL`); see [`is_null`].
1162    #[allow(clippy::wrong_self_convention)]
1163    #[allow(clippy::type_complexity)]
1164    fn is_null(
1165        self,
1166    ) -> SQLExpr<
1167        'a,
1168        V,
1169        <V::DialectMarker as DialectTypes>::Bool,
1170        NonNull,
1171        Self::Aggregate,
1172        ScopeOnly<Self::Sources>,
1173    > {
1174        is_null(self)
1175    }
1176
1177    /// Non-NULL check (`IS NOT NULL`); see [`is_not_null`].
1178    #[allow(clippy::wrong_self_convention)]
1179    #[allow(clippy::type_complexity)]
1180    fn is_not_null(
1181        self,
1182    ) -> SQLExpr<
1183        'a,
1184        V,
1185        <V::DialectMarker as DialectTypes>::Bool,
1186        NonNull,
1187        Self::Aggregate,
1188        ScopeOnly<Self::Sources>,
1189    > {
1190        is_not_null(self)
1191    }
1192
1193    /// Inclusive range check (`BETWEEN`); see [`between`].
1194    #[allow(clippy::type_complexity)]
1195    fn between<L, H>(
1196        self,
1197        low: L,
1198        high: H,
1199    ) -> SQLExpr<
1200        'a,
1201        V,
1202        <V::DialectMarker as DialectTypes>::Bool,
1203        NonNull,
1204        <<Self::Aggregate as AggregateKind>::Or<<L as ComparisonOperand<'a, V, Self>>::Aggregate,> as AggregateKind>::Or<<H as ComparisonOperand<'a, V, Self>>::Aggregate,>,
1205        BetweenSources<'a, V, Self, L, H>,
1206    >
1207    where
1208        L: ComparisonOperand<'a, V, Self>,
1209        H: ComparisonOperand<'a, V, Self>,
1210        Self::SQLType: Compatible<<L as ComparisonOperand<'a, V, Self>>::SQLType>,
1211        Self::SQLType: Compatible<<H as ComparisonOperand<'a, V, Self>>::SQLType>,
1212{
1213        between(self, low, high)
1214    }
1215
1216    /// Negated range check (`NOT BETWEEN`); see [`not_between`].
1217    #[allow(clippy::type_complexity)]
1218    fn not_between<L, H>(
1219        self,
1220        low: L,
1221        high: H,
1222    ) -> SQLExpr<
1223        'a,
1224        V,
1225        <V::DialectMarker as DialectTypes>::Bool,
1226        NonNull,
1227        <<Self::Aggregate as AggregateKind>::Or<<L as ComparisonOperand<'a, V, Self>>::Aggregate,> as AggregateKind>::Or<<H as ComparisonOperand<'a, V, Self>>::Aggregate,>,
1228        BetweenSources<'a, V, Self, L, H>,
1229    >
1230    where
1231        L: ComparisonOperand<'a, V, Self>,
1232        H: ComparisonOperand<'a, V, Self>,
1233        Self::SQLType: Compatible<<L as ComparisonOperand<'a, V, Self>>::SQLType>,
1234        Self::SQLType: Compatible<<H as ComparisonOperand<'a, V, Self>>::SQLType>,
1235{
1236        not_between(self, low, high)
1237    }
1238
1239    /// Membership in a list of values (`IN (...)`); see [`in_array`](super::in_array).
1240    #[allow(clippy::type_complexity)]
1241    fn in_array<I, R>(
1242        self,
1243        values: I,
1244    ) -> SQLExpr<
1245        'a,
1246        V,
1247        <V::DialectMarker as DialectTypes>::Bool,
1248        NonNull,
1249        Self::Aggregate,
1250        (
1251            Arg<Self::Nullable, Self::Sources>,
1252            Arg<R::Nullable, R::Sources>,
1253        ),
1254    >
1255    where
1256        I: IntoIterator<Item = R>,
1257        R: Expr<'a, V>,
1258        Self::SQLType: Compatible<R::SQLType>,
1259    {
1260        crate::expr::in_array(self, values)
1261    }
1262
1263    /// Non-membership in a list of values (`NOT IN (...)`); see
1264    /// [`not_in_array`](super::not_in_array).
1265    #[allow(clippy::type_complexity)]
1266    fn not_in_array<I, R>(
1267        self,
1268        values: I,
1269    ) -> SQLExpr<
1270        'a,
1271        V,
1272        <V::DialectMarker as DialectTypes>::Bool,
1273        NonNull,
1274        Self::Aggregate,
1275        (
1276            Arg<Self::Nullable, Self::Sources>,
1277            Arg<R::Nullable, R::Sources>,
1278        ),
1279    >
1280    where
1281        I: IntoIterator<Item = R>,
1282        R: Expr<'a, V>,
1283        Self::SQLType: Compatible<R::SQLType>,
1284    {
1285        crate::expr::not_in_array(self, values)
1286    }
1287
1288    /// Membership in a subquery's rows (`IN (SELECT ...)`); see
1289    /// [`in_subquery`](super::in_subquery).
1290    #[allow(clippy::type_complexity)]
1291    fn in_subquery<S>(
1292        self,
1293        subquery: S,
1294    ) -> SQLExpr<
1295        'a,
1296        V,
1297        <V::DialectMarker as DialectTypes>::Bool,
1298        NonNull,
1299        Self::Aggregate,
1300        (
1301            Arg<Self::Nullable, Self::Sources>,
1302            Arg<S::Nullable, S::Sources>,
1303        ),
1304    >
1305    where
1306        S: Expr<'a, V>,
1307        Self::SQLType: Compatible<S::SQLType> + Compatible<Self::SQLType>,
1308    {
1309        crate::expr::in_subquery(self, subquery)
1310    }
1311
1312    /// Non-membership in a subquery's rows (`NOT IN (SELECT ...)`); see
1313    /// [`not_in_subquery`](super::not_in_subquery).
1314    #[allow(clippy::type_complexity)]
1315    fn not_in_subquery<S>(
1316        self,
1317        subquery: S,
1318    ) -> SQLExpr<
1319        'a,
1320        V,
1321        <V::DialectMarker as DialectTypes>::Bool,
1322        NonNull,
1323        Self::Aggregate,
1324        (
1325            Arg<Self::Nullable, Self::Sources>,
1326            Arg<S::Nullable, S::Sources>,
1327        ),
1328    >
1329    where
1330        S: Expr<'a, V>,
1331        Self::SQLType: Compatible<S::SQLType> + Compatible<Self::SQLType>,
1332    {
1333        crate::expr::not_in_subquery(self, subquery)
1334    }
1335
1336    /// NULL-safe inequality (`IS DISTINCT FROM`); see [`is_distinct_from`].
1337    #[allow(clippy::type_complexity, clippy::wrong_self_convention)]
1338    fn is_distinct_from<R>(
1339        self,
1340        other: R,
1341    ) -> SQLExpr<
1342        'a,
1343        V,
1344        <V::DialectMarker as DialectTypes>::Bool,
1345        NonNull,
1346        <Self::Aggregate as AggregateKind>::Or<<R as ComparisonOperand<'a, V, Self>>::Aggregate>,
1347        NullSafeCmpSources<'a, V, Self, R>,
1348    >
1349    where
1350        R: ComparisonOperand<'a, V, Self>,
1351        Self::SQLType: Compatible<<R as ComparisonOperand<'a, V, Self>>::SQLType>,
1352    {
1353        is_distinct_from(self, other)
1354    }
1355
1356    /// NULL-safe equality (`IS NOT DISTINCT FROM`); see [`is_not_distinct_from`].
1357    #[allow(clippy::type_complexity, clippy::wrong_self_convention)]
1358    fn is_not_distinct_from<R>(
1359        self,
1360        other: R,
1361    ) -> SQLExpr<
1362        'a,
1363        V,
1364        <V::DialectMarker as DialectTypes>::Bool,
1365        NonNull,
1366        <Self::Aggregate as AggregateKind>::Or<<R as ComparisonOperand<'a, V, Self>>::Aggregate>,
1367        NullSafeCmpSources<'a, V, Self, R>,
1368    >
1369    where
1370        R: ComparisonOperand<'a, V, Self>,
1371        Self::SQLType: Compatible<<R as ComparisonOperand<'a, V, Self>>::SQLType>,
1372    {
1373        is_not_distinct_from(self, other)
1374    }
1375
1376    /// Truth test (`IS TRUE`); see [`is_true`].
1377    #[allow(clippy::wrong_self_convention)]
1378    #[allow(clippy::type_complexity)]
1379    fn is_true(
1380        self,
1381    ) -> SQLExpr<
1382        'a,
1383        V,
1384        <V::DialectMarker as DialectTypes>::Bool,
1385        NonNull,
1386        Self::Aggregate,
1387        ScopeOnly<Self::Sources>,
1388    > {
1389        is_true(self)
1390    }
1391
1392    /// Falsity test (`IS FALSE`); see [`is_false`].
1393    #[allow(clippy::wrong_self_convention)]
1394    #[allow(clippy::type_complexity)]
1395    fn is_false(
1396        self,
1397    ) -> SQLExpr<
1398        'a,
1399        V,
1400        <V::DialectMarker as DialectTypes>::Bool,
1401        NonNull,
1402        Self::Aggregate,
1403        ScopeOnly<Self::Sources>,
1404    > {
1405        is_false(self)
1406    }
1407}
1408
1409impl<'a, V: SQLParam, E: Expr<'a, V>> ExprExt<'a, V> for E {}