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 {}