Skip to main content

drizzle_core/expr/
cmp.rs

1//! Type-safe comparison functions.
2//!
3//! This module provides both function-based and method-based comparisons:
4//!
5//! ```rust
6//! # let _ = r####"
7//! // Function style
8//! eq(users.id, 42)
9//! gt(users.age, 18)
10//!
11//! // Method style (on SQLExpr)
12//! users.id.eq(42)
13//! users.age.gt(18)
14//! # "####;
15//! ```
16//!
17//! # Type Safety
18//!
19//! - `eq`, `ne`, `gt`, `gte`, `lt`, `lte`: Require compatible types
20//! - `like`, `not_like`: Require textual types on both sides
21//! - `between`: Requires expr compatible with both bounds
22//! - `is_null`, `is_not_null`: No type constraint (any type can be null-checked)
23
24use crate::dialect::DialectTypes;
25use crate::sql::{SQL, Token};
26use crate::traits::SQLParam;
27use crate::types::{Compatible, DataType, Textual};
28
29use super::{AggOr, AggregateKind, Expr, NonNull, SQLExpr};
30
31// =============================================================================
32// Internal Helper
33// =============================================================================
34
35fn binary_op<'a, V, L, R>(left: L, operator: Token, right: R) -> SQL<'a, V>
36where
37    V: SQLParam + 'a,
38    L: Expr<'a, V>,
39    R: ComparisonOperand<'a, V, L>,
40    L::SQLType: Compatible<<R as ComparisonOperand<'a, V, L>>::SQLType>,
41{
42    let left_sql = operand_sql(left);
43    let right_sql = ComparisonOperand::into_comparison_sql(right);
44
45    left_sql.push(operator).append(right_sql)
46}
47
48#[inline]
49fn operand_sql<'a, V, T>(value: T) -> SQL<'a, V>
50where
51    V: SQLParam + 'a,
52    T: Expr<'a, V>,
53{
54    value.into_expr_sql()
55}
56
57/// Type-safe operand for comparison functions.
58///
59/// This trait exists as an indirection layer between comparison functions
60/// (`eq`, `gt`, `like`, etc.) and the `Expr` trait. Rather than accepting
61/// any `Expr` directly, comparisons require `ComparisonOperand<'a, V, L>`
62/// where `L` is the left-hand expression type.
63///
64/// The blanket impl below only fires when `L::SQLType: Compatible<R::SQLType>`.
65/// Table macros can also implement this trait for custom column value types and
66/// a specific generated column ZST, which enables `eq(table.custom, value)`
67/// without making the value type a global SQL expression.
68pub trait ComparisonOperand<'a, V, L>: Sized
69where
70    V: SQLParam + 'a,
71    L: Expr<'a, V>,
72{
73    type SQLType: DataType;
74    type Aggregate: AggregateKind;
75
76    fn into_comparison_sql(self) -> SQL<'a, V>;
77}
78
79impl<'a, V, L, R> ComparisonOperand<'a, V, L> for R
80where
81    V: SQLParam + 'a,
82    L: Expr<'a, V>,
83    R: Expr<'a, V>,
84    L::SQLType: Compatible<R::SQLType>,
85{
86    type SQLType = R::SQLType;
87    type Aggregate = R::Aggregate;
88
89    fn into_comparison_sql(self) -> SQL<'a, V> {
90        self.into_expr_sql()
91    }
92}
93
94// =============================================================================
95// Equality Comparisons
96// =============================================================================
97
98/// Equality comparison (`=`).
99///
100/// Requires both operands to have compatible SQL types.
101///
102/// # Type Safety
103///
104/// ```rust
105/// # let _ = r####"
106/// // ✅ OK: Int compared with i32
107/// eq(users.id, 10);
108///
109/// // ✅ OK: Int compared with BigInt (integer family)
110/// eq(users.id, users.big_id);
111///
112/// // ❌ Compile error: Int cannot be compared with Text
113/// eq(users.id, "hello");
114/// # "####;
115/// ```
116#[allow(clippy::type_complexity)]
117pub fn eq<'a, V, L, R>(
118    left: L,
119    right: R,
120) -> SQLExpr<
121    'a,
122    V,
123    <V::DialectMarker as DialectTypes>::Bool,
124    NonNull,
125    <L::Aggregate as AggOr<<R as ComparisonOperand<'a, V, L>>::Aggregate>>::Output,
126>
127where
128    V: SQLParam + 'a,
129    L: Expr<'a, V>,
130    R: ComparisonOperand<'a, V, L>,
131    L::SQLType: Compatible<<R as ComparisonOperand<'a, V, L>>::SQLType>,
132    L::Aggregate: AggOr<<R as ComparisonOperand<'a, V, L>>::Aggregate>,
133{
134    SQLExpr::new(binary_op(left, Token::EQ, right))
135}
136
137/// Inequality comparison (`<>`).
138///
139/// Requires both operands to have compatible SQL types.
140#[allow(clippy::type_complexity)]
141pub fn ne<'a, V, L, R>(
142    left: L,
143    right: R,
144) -> SQLExpr<
145    'a,
146    V,
147    <V::DialectMarker as DialectTypes>::Bool,
148    NonNull,
149    <L::Aggregate as AggOr<<R as ComparisonOperand<'a, V, L>>::Aggregate>>::Output,
150>
151where
152    V: SQLParam + 'a,
153    L: Expr<'a, V>,
154    R: ComparisonOperand<'a, V, L>,
155    L::SQLType: Compatible<<R as ComparisonOperand<'a, V, L>>::SQLType>,
156    L::Aggregate: AggOr<<R as ComparisonOperand<'a, V, L>>::Aggregate>,
157{
158    SQLExpr::new(binary_op(left, Token::NE, right))
159}
160
161/// Inequality comparison (`<>`).
162///
163/// Alias for [`ne`].
164#[allow(clippy::type_complexity)]
165pub fn neq<'a, V, L, R>(
166    left: L,
167    right: R,
168) -> SQLExpr<
169    'a,
170    V,
171    <V::DialectMarker as DialectTypes>::Bool,
172    NonNull,
173    <L::Aggregate as AggOr<<R as ComparisonOperand<'a, V, L>>::Aggregate>>::Output,
174>
175where
176    V: SQLParam + 'a,
177    L: Expr<'a, V>,
178    R: ComparisonOperand<'a, V, L>,
179    L::SQLType: Compatible<<R as ComparisonOperand<'a, V, L>>::SQLType>,
180    L::Aggregate: AggOr<<R as ComparisonOperand<'a, V, L>>::Aggregate>,
181{
182    ne(left, right)
183}
184
185// =============================================================================
186// Ordering Comparisons
187// =============================================================================
188
189/// Greater-than comparison (`>`).
190///
191/// Requires both operands to have compatible SQL types.
192#[allow(clippy::type_complexity)]
193pub fn gt<'a, V, L, R>(
194    left: L,
195    right: R,
196) -> SQLExpr<
197    'a,
198    V,
199    <V::DialectMarker as DialectTypes>::Bool,
200    NonNull,
201    <L::Aggregate as AggOr<<R as ComparisonOperand<'a, V, L>>::Aggregate>>::Output,
202>
203where
204    V: SQLParam + 'a,
205    L: Expr<'a, V>,
206    R: ComparisonOperand<'a, V, L>,
207    L::SQLType: Compatible<<R as ComparisonOperand<'a, V, L>>::SQLType>,
208    L::Aggregate: AggOr<<R as ComparisonOperand<'a, V, L>>::Aggregate>,
209{
210    SQLExpr::new(binary_op(left, Token::GT, right))
211}
212
213/// Greater-than-or-equal comparison (`>=`).
214///
215/// Requires both operands to have compatible SQL types.
216#[allow(clippy::type_complexity)]
217pub fn gte<'a, V, L, R>(
218    left: L,
219    right: R,
220) -> SQLExpr<
221    'a,
222    V,
223    <V::DialectMarker as DialectTypes>::Bool,
224    NonNull,
225    <L::Aggregate as AggOr<<R as ComparisonOperand<'a, V, L>>::Aggregate>>::Output,
226>
227where
228    V: SQLParam + 'a,
229    L: Expr<'a, V>,
230    R: ComparisonOperand<'a, V, L>,
231    L::SQLType: Compatible<<R as ComparisonOperand<'a, V, L>>::SQLType>,
232    L::Aggregate: AggOr<<R as ComparisonOperand<'a, V, L>>::Aggregate>,
233{
234    SQLExpr::new(binary_op(left, Token::GE, right))
235}
236
237/// Less-than comparison (`<`).
238///
239/// Requires both operands to have compatible SQL types.
240#[allow(clippy::type_complexity)]
241pub fn lt<'a, V, L, R>(
242    left: L,
243    right: R,
244) -> SQLExpr<
245    'a,
246    V,
247    <V::DialectMarker as DialectTypes>::Bool,
248    NonNull,
249    <L::Aggregate as AggOr<<R as ComparisonOperand<'a, V, L>>::Aggregate>>::Output,
250>
251where
252    V: SQLParam + 'a,
253    L: Expr<'a, V>,
254    R: ComparisonOperand<'a, V, L>,
255    L::SQLType: Compatible<<R as ComparisonOperand<'a, V, L>>::SQLType>,
256    L::Aggregate: AggOr<<R as ComparisonOperand<'a, V, L>>::Aggregate>,
257{
258    SQLExpr::new(binary_op(left, Token::LT, right))
259}
260
261/// Less-than-or-equal comparison (`<=`).
262///
263/// Requires both operands to have compatible SQL types.
264#[allow(clippy::type_complexity)]
265pub fn lte<'a, V, L, R>(
266    left: L,
267    right: R,
268) -> SQLExpr<
269    'a,
270    V,
271    <V::DialectMarker as DialectTypes>::Bool,
272    NonNull,
273    <L::Aggregate as AggOr<<R as ComparisonOperand<'a, V, L>>::Aggregate>>::Output,
274>
275where
276    V: SQLParam + 'a,
277    L: Expr<'a, V>,
278    R: ComparisonOperand<'a, V, L>,
279    L::SQLType: Compatible<<R as ComparisonOperand<'a, V, L>>::SQLType>,
280    L::Aggregate: AggOr<<R as ComparisonOperand<'a, V, L>>::Aggregate>,
281{
282    SQLExpr::new(binary_op(left, Token::LE, right))
283}
284
285// =============================================================================
286// Pattern Matching
287// =============================================================================
288
289/// LIKE pattern matching.
290///
291/// Requires both operands to be textual types (TEXT, VARCHAR).
292///
293/// # Type Safety
294///
295/// ```rust
296/// # let _ = r####"
297/// // ✅ OK: Text column with text pattern
298/// like(users.name, "%Alice%");
299///
300/// // ❌ Compile error: Int is not Textual
301/// like(users.id, "%123%");
302/// # "####;
303/// ```
304#[allow(clippy::type_complexity)]
305pub fn like<'a, V, L, R>(
306    left: L,
307    pattern: R,
308) -> SQLExpr<
309    'a,
310    V,
311    <V::DialectMarker as DialectTypes>::Bool,
312    NonNull,
313    <L::Aggregate as AggOr<<R as ComparisonOperand<'a, V, L>>::Aggregate>>::Output,
314>
315where
316    V: SQLParam + 'a,
317    L: Expr<'a, V>,
318    R: ComparisonOperand<'a, V, L>,
319    L::SQLType: Compatible<<R as ComparisonOperand<'a, V, L>>::SQLType>,
320    L::SQLType: Textual,
321    <R as ComparisonOperand<'a, V, L>>::SQLType: Textual,
322    L::Aggregate: AggOr<<R as ComparisonOperand<'a, V, L>>::Aggregate>,
323{
324    SQLExpr::new(
325        operand_sql(left)
326            .push(Token::LIKE)
327            .append(ComparisonOperand::into_comparison_sql(pattern)),
328    )
329}
330
331/// NOT LIKE pattern matching.
332///
333/// Requires both operands to be textual types (TEXT, VARCHAR).
334#[allow(clippy::type_complexity)]
335pub fn not_like<'a, V, L, R>(
336    left: L,
337    pattern: R,
338) -> SQLExpr<
339    'a,
340    V,
341    <V::DialectMarker as DialectTypes>::Bool,
342    NonNull,
343    <L::Aggregate as AggOr<<R as ComparisonOperand<'a, V, L>>::Aggregate>>::Output,
344>
345where
346    V: SQLParam + 'a,
347    L: Expr<'a, V>,
348    R: ComparisonOperand<'a, V, L>,
349    L::SQLType: Compatible<<R as ComparisonOperand<'a, V, L>>::SQLType>,
350    L::SQLType: Textual,
351    <R as ComparisonOperand<'a, V, L>>::SQLType: Textual,
352    L::Aggregate: AggOr<<R as ComparisonOperand<'a, V, L>>::Aggregate>,
353{
354    SQLExpr::new(
355        operand_sql(left)
356            .push(Token::NOT)
357            .push(Token::LIKE)
358            .append(ComparisonOperand::into_comparison_sql(pattern)),
359    )
360}
361
362// =============================================================================
363// Range Comparisons
364// =============================================================================
365
366/// BETWEEN comparison.
367///
368/// Checks if expr is between low and high (inclusive).
369/// Requires expr type to be compatible with both bounds.
370#[allow(clippy::type_complexity)]
371pub fn between<'a, V, E, L, H>(
372    expr: E,
373    low: L,
374    high: H,
375) -> SQLExpr<
376    'a,
377    V,
378    <V::DialectMarker as DialectTypes>::Bool,
379    NonNull,
380    <<E::Aggregate as AggOr<<L as ComparisonOperand<'a, V, E>>::Aggregate>>::Output as AggOr<
381        <H as ComparisonOperand<'a, V, E>>::Aggregate,
382    >>::Output,
383>
384where
385    V: SQLParam + 'a,
386    E: Expr<'a, V>,
387    L: ComparisonOperand<'a, V, E>,
388    H: ComparisonOperand<'a, V, E>,
389    E::SQLType: Compatible<<L as ComparisonOperand<'a, V, E>>::SQLType>,
390    E::SQLType: Compatible<<H as ComparisonOperand<'a, V, E>>::SQLType>,
391    E::Aggregate: AggOr<<L as ComparisonOperand<'a, V, E>>::Aggregate>,
392    <E::Aggregate as AggOr<<L as ComparisonOperand<'a, V, E>>::Aggregate>>::Output:
393        AggOr<<H as ComparisonOperand<'a, V, E>>::Aggregate>,
394{
395    SQLExpr::new(
396        SQL::from(Token::LPAREN)
397            .append(operand_sql(expr))
398            .push(Token::BETWEEN)
399            .append(ComparisonOperand::into_comparison_sql(low))
400            .push(Token::AND)
401            .append(ComparisonOperand::into_comparison_sql(high))
402            .push(Token::RPAREN),
403    )
404}
405
406/// NOT BETWEEN comparison.
407///
408/// Requires expr type to be compatible with both bounds.
409#[allow(clippy::type_complexity)]
410pub fn not_between<'a, V, E, L, H>(
411    expr: E,
412    low: L,
413    high: H,
414) -> SQLExpr<
415    'a,
416    V,
417    <V::DialectMarker as DialectTypes>::Bool,
418    NonNull,
419    <<E::Aggregate as AggOr<<L as ComparisonOperand<'a, V, E>>::Aggregate>>::Output as AggOr<
420        <H as ComparisonOperand<'a, V, E>>::Aggregate,
421    >>::Output,
422>
423where
424    V: SQLParam + 'a,
425    E: Expr<'a, V>,
426    L: ComparisonOperand<'a, V, E>,
427    H: ComparisonOperand<'a, V, E>,
428    E::SQLType: Compatible<<L as ComparisonOperand<'a, V, E>>::SQLType>,
429    E::SQLType: Compatible<<H as ComparisonOperand<'a, V, E>>::SQLType>,
430    E::Aggregate: AggOr<<L as ComparisonOperand<'a, V, E>>::Aggregate>,
431    <E::Aggregate as AggOr<<L as ComparisonOperand<'a, V, E>>::Aggregate>>::Output:
432        AggOr<<H as ComparisonOperand<'a, V, E>>::Aggregate>,
433{
434    SQLExpr::new(
435        SQL::from(Token::LPAREN)
436            .append(operand_sql(expr))
437            .push(Token::NOT)
438            .push(Token::BETWEEN)
439            .append(ComparisonOperand::into_comparison_sql(low))
440            .push(Token::AND)
441            .append(ComparisonOperand::into_comparison_sql(high))
442            .push(Token::RPAREN),
443    )
444}
445
446// =============================================================================
447// NULL Checks
448// =============================================================================
449
450/// IS NULL check.
451///
452/// Returns a boolean expression checking if the value is NULL.
453/// Any expression type can be null-checked.
454pub fn is_null<'a, V, E>(
455    expr: E,
456) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, E::Aggregate>
457where
458    V: SQLParam + 'a,
459    E: Expr<'a, V>,
460{
461    SQLExpr::new(operand_sql(expr).push(Token::IS).push(Token::NULL))
462}
463
464/// IS NOT NULL check.
465///
466/// Returns a boolean expression checking if the value is not NULL.
467/// Any expression type can be null-checked.
468pub fn is_not_null<'a, V, E>(
469    expr: E,
470) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, E::Aggregate>
471where
472    V: SQLParam + 'a,
473    E: Expr<'a, V>,
474{
475    SQLExpr::new(
476        operand_sql(expr)
477            .push(Token::IS)
478            .push(Token::NOT)
479            .push(Token::NULL),
480    )
481}
482
483// =============================================================================
484// IS DISTINCT FROM
485// =============================================================================
486
487/// IS DISTINCT FROM - NULL-safe inequality comparison.
488///
489/// Unlike `<>`, this treats NULL as a comparable value:
490/// - `NULL IS DISTINCT FROM NULL` → false
491/// - `NULL IS DISTINCT FROM 5` → true
492/// - `5 IS DISTINCT FROM NULL` → true
493///
494/// Supported by both `SQLite` (3.39+) and `PostgreSQL`.
495#[allow(clippy::type_complexity)]
496pub fn is_distinct_from<'a, V, L, R>(
497    left: L,
498    right: R,
499) -> SQLExpr<
500    'a,
501    V,
502    <V::DialectMarker as DialectTypes>::Bool,
503    NonNull,
504    <L::Aggregate as AggOr<<R as ComparisonOperand<'a, V, L>>::Aggregate>>::Output,
505>
506where
507    V: SQLParam + 'a,
508    L: Expr<'a, V>,
509    R: ComparisonOperand<'a, V, L>,
510    L::SQLType: Compatible<<R as ComparisonOperand<'a, V, L>>::SQLType>,
511    L::Aggregate: AggOr<<R as ComparisonOperand<'a, V, L>>::Aggregate>,
512{
513    SQLExpr::new(
514        operand_sql(left)
515            .push(Token::IS)
516            .push(Token::DISTINCT)
517            .push(Token::FROM)
518            .append(ComparisonOperand::into_comparison_sql(right)),
519    )
520}
521
522/// IS NOT DISTINCT FROM - NULL-safe equality comparison.
523///
524/// Unlike `=`, this treats NULL as a comparable value:
525/// - `NULL IS NOT DISTINCT FROM NULL` → true
526/// - `NULL IS NOT DISTINCT FROM 5` → false
527///
528/// Supported by both `SQLite` (3.39+) and `PostgreSQL`.
529#[allow(clippy::type_complexity)]
530pub fn is_not_distinct_from<'a, V, L, R>(
531    left: L,
532    right: R,
533) -> SQLExpr<
534    'a,
535    V,
536    <V::DialectMarker as DialectTypes>::Bool,
537    NonNull,
538    <L::Aggregate as AggOr<<R as ComparisonOperand<'a, V, L>>::Aggregate>>::Output,
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::Aggregate: AggOr<<R as ComparisonOperand<'a, V, L>>::Aggregate>,
546{
547    SQLExpr::new(
548        operand_sql(left)
549            .push(Token::IS)
550            .push(Token::NOT)
551            .push(Token::DISTINCT)
552            .push(Token::FROM)
553            .append(ComparisonOperand::into_comparison_sql(right)),
554    )
555}
556
557// =============================================================================
558// Boolean Testing
559// =============================================================================
560
561/// IS TRUE - tests if a boolean expression is true.
562///
563/// Unlike `= TRUE`, this handles NULL correctly:
564/// - `TRUE IS TRUE` → true
565/// - `FALSE IS TRUE` → false
566/// - `NULL IS TRUE` → false (not NULL!)
567pub fn is_true<'a, V, E>(
568    expr: E,
569) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, E::Aggregate>
570where
571    V: SQLParam + 'a,
572    E: Expr<'a, V>,
573{
574    SQLExpr::new(operand_sql(expr).push(Token::IS).append(SQL::raw("TRUE")))
575}
576
577/// IS FALSE - tests if a boolean expression is false.
578///
579/// Unlike `= FALSE`, this handles NULL correctly:
580/// - `FALSE IS FALSE` → true
581/// - `TRUE IS FALSE` → false
582/// - `NULL IS FALSE` → false (not NULL!)
583pub fn is_false<'a, V, E>(
584    expr: E,
585) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, E::Aggregate>
586where
587    V: SQLParam + 'a,
588    E: Expr<'a, V>,
589{
590    SQLExpr::new(operand_sql(expr).push(Token::IS).append(SQL::raw("FALSE")))
591}
592
593// =============================================================================
594// Method-based Comparison API (Extension Trait)
595// =============================================================================
596
597/// Extension trait providing method-based comparisons for any `Expr` type.
598///
599/// This trait is blanket-implemented for all types implementing `Expr`,
600/// allowing method syntax on columns, literals, and expressions:
601///
602/// ```rust
603/// # let _ = r####"
604/// // Works on columns directly
605/// users.id.eq(42)
606/// users.age.gt(18)
607///
608/// // Chain with operators
609/// users.id.eq(42) & users.age.gt(18)
610/// # "####;
611/// ```
612pub trait ExprExt<'a, V: SQLParam>: Expr<'a, V> + Sized {
613    /// Equality comparison (`=`).
614    ///
615    /// ```rust
616    /// # let _ = r####"
617    /// users.id.eq(42)  // "users"."id" = 42
618    /// # "####;
619    /// ```
620    #[allow(clippy::type_complexity)]
621    fn eq<R>(
622        self,
623        other: R,
624    ) -> SQLExpr<
625        'a,
626        V,
627        <V::DialectMarker as DialectTypes>::Bool,
628        NonNull,
629        <Self::Aggregate as AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>>::Output,
630    >
631    where
632        R: ComparisonOperand<'a, V, Self>,
633        Self::SQLType: Compatible<<R as ComparisonOperand<'a, V, Self>>::SQLType>,
634        Self::Aggregate: AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>,
635    {
636        eq(self, other)
637    }
638
639    /// Inequality comparison (`<>`).
640    ///
641    /// ```rust
642    /// # let _ = r####"
643    /// users.id.ne(42)  // "users"."id" <> 42
644    /// # "####;
645    /// ```
646    #[allow(clippy::type_complexity)]
647    fn ne<R>(
648        self,
649        other: R,
650    ) -> SQLExpr<
651        'a,
652        V,
653        <V::DialectMarker as DialectTypes>::Bool,
654        NonNull,
655        <Self::Aggregate as AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>>::Output,
656    >
657    where
658        R: ComparisonOperand<'a, V, Self>,
659        Self::SQLType: Compatible<<R as ComparisonOperand<'a, V, Self>>::SQLType>,
660        Self::Aggregate: AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>,
661    {
662        ne(self, other)
663    }
664
665    /// Greater-than comparison (`>`).
666    ///
667    /// ```rust
668    /// # let _ = r####"
669    /// users.age.gt(18)  // "users"."age" > 18
670    /// # "####;
671    /// ```
672    #[allow(clippy::type_complexity)]
673    fn gt<R>(
674        self,
675        other: R,
676    ) -> SQLExpr<
677        'a,
678        V,
679        <V::DialectMarker as DialectTypes>::Bool,
680        NonNull,
681        <Self::Aggregate as AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>>::Output,
682    >
683    where
684        R: ComparisonOperand<'a, V, Self>,
685        Self::SQLType: Compatible<<R as ComparisonOperand<'a, V, Self>>::SQLType>,
686        Self::Aggregate: AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>,
687    {
688        gt(self, other)
689    }
690
691    /// Greater-than-or-equal comparison (`>=`).
692    ///
693    /// ```rust
694    /// # let _ = r####"
695    /// users.age.ge(18)  // "users"."age" >= 18
696    /// # "####;
697    /// ```
698    #[allow(clippy::type_complexity)]
699    fn ge<R>(
700        self,
701        other: R,
702    ) -> SQLExpr<
703        'a,
704        V,
705        <V::DialectMarker as DialectTypes>::Bool,
706        NonNull,
707        <Self::Aggregate as AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>>::Output,
708    >
709    where
710        R: ComparisonOperand<'a, V, Self>,
711        Self::SQLType: Compatible<<R as ComparisonOperand<'a, V, Self>>::SQLType>,
712        Self::Aggregate: AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>,
713    {
714        gte(self, other)
715    }
716
717    /// Less-than comparison (`<`).
718    ///
719    /// ```rust
720    /// # let _ = r####"
721    /// users.age.lt(65)  // "users"."age" < 65
722    /// # "####;
723    /// ```
724    #[allow(clippy::type_complexity)]
725    fn lt<R>(
726        self,
727        other: R,
728    ) -> SQLExpr<
729        'a,
730        V,
731        <V::DialectMarker as DialectTypes>::Bool,
732        NonNull,
733        <Self::Aggregate as AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>>::Output,
734    >
735    where
736        R: ComparisonOperand<'a, V, Self>,
737        Self::SQLType: Compatible<<R as ComparisonOperand<'a, V, Self>>::SQLType>,
738        Self::Aggregate: AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>,
739    {
740        lt(self, other)
741    }
742
743    /// Less-than-or-equal comparison (`<=`).
744    ///
745    /// ```rust
746    /// # let _ = r####"
747    /// users.age.le(65)  // "users"."age" <= 65
748    /// # "####;
749    /// ```
750    #[allow(clippy::type_complexity)]
751    fn le<R>(
752        self,
753        other: R,
754    ) -> SQLExpr<
755        'a,
756        V,
757        <V::DialectMarker as DialectTypes>::Bool,
758        NonNull,
759        <Self::Aggregate as AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>>::Output,
760    >
761    where
762        R: ComparisonOperand<'a, V, Self>,
763        Self::SQLType: Compatible<<R as ComparisonOperand<'a, V, Self>>::SQLType>,
764        Self::Aggregate: AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>,
765    {
766        lte(self, other)
767    }
768
769    /// LIKE pattern matching.
770    ///
771    /// ```rust
772    /// # let _ = r####"
773    /// users.name.like("%Alice%")  // "users"."name" LIKE '%Alice%'
774    /// # "####;
775    /// ```
776    #[allow(clippy::type_complexity)]
777    fn like<R>(
778        self,
779        pattern: R,
780    ) -> SQLExpr<
781        'a,
782        V,
783        <V::DialectMarker as DialectTypes>::Bool,
784        NonNull,
785        <Self::Aggregate as AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>>::Output,
786    >
787    where
788        R: ComparisonOperand<'a, V, Self>,
789        Self::SQLType: Compatible<<R as ComparisonOperand<'a, V, Self>>::SQLType>,
790        Self::SQLType: Textual,
791        <R as ComparisonOperand<'a, V, Self>>::SQLType: Textual,
792        Self::Aggregate: AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>,
793    {
794        like(self, pattern)
795    }
796
797    /// NOT LIKE pattern matching.
798    ///
799    /// ```rust
800    /// # let _ = r####"
801    /// users.name.not_like("%Bot%")  // "users"."name" NOT LIKE '%Bot%'
802    /// # "####;
803    /// ```
804    #[allow(clippy::type_complexity)]
805    fn not_like<R>(
806        self,
807        pattern: R,
808    ) -> SQLExpr<
809        'a,
810        V,
811        <V::DialectMarker as DialectTypes>::Bool,
812        NonNull,
813        <Self::Aggregate as AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>>::Output,
814    >
815    where
816        R: ComparisonOperand<'a, V, Self>,
817        Self::SQLType: Compatible<<R as ComparisonOperand<'a, V, Self>>::SQLType>,
818        Self::SQLType: Textual,
819        <R as ComparisonOperand<'a, V, Self>>::SQLType: Textual,
820        Self::Aggregate: AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>,
821    {
822        not_like(self, pattern)
823    }
824
825    /// IS NULL check.
826    ///
827    /// ```rust
828    /// # let _ = r####"
829    /// users.deleted_at.is_null()  // "users"."deleted_at" IS NULL
830    /// # "####;
831    /// ```
832    #[allow(clippy::wrong_self_convention)]
833    fn is_null(
834        self,
835    ) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, Self::Aggregate> {
836        is_null(self)
837    }
838
839    /// IS NOT NULL check.
840    ///
841    /// ```rust
842    /// # let _ = r####"
843    /// users.email.is_not_null()  // "users"."email" IS NOT NULL
844    /// # "####;
845    /// ```
846    #[allow(clippy::wrong_self_convention)]
847    fn is_not_null(
848        self,
849    ) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, Self::Aggregate> {
850        is_not_null(self)
851    }
852
853    /// BETWEEN comparison.
854    ///
855    /// Checks if the value is between low and high (inclusive).
856    ///
857    /// ```rust
858    /// # let _ = r####"
859    /// users.age.between(18, 65)  // ("users"."age" BETWEEN 18 AND 65)
860    /// # "####;
861    /// ```
862    #[allow(clippy::type_complexity)]
863    fn between<L, H>(
864        self,
865        low: L,
866        high: H,
867    ) -> SQLExpr<
868        'a,
869        V,
870        <V::DialectMarker as DialectTypes>::Bool,
871        NonNull,
872        <<Self::Aggregate as AggOr<
873            <L as ComparisonOperand<'a, V, Self>>::Aggregate,
874        >>::Output as AggOr<
875            <H as ComparisonOperand<'a, V, Self>>::Aggregate,
876        >>::Output,
877    >
878    where
879        L: ComparisonOperand<'a, V, Self>,
880        H: ComparisonOperand<'a, V, Self>,
881        Self::SQLType: Compatible<<L as ComparisonOperand<'a, V, Self>>::SQLType>,
882        Self::SQLType: Compatible<<H as ComparisonOperand<'a, V, Self>>::SQLType>,
883        Self::Aggregate:
884            AggOr<<L as ComparisonOperand<'a, V, Self>>::Aggregate>,
885        <Self::Aggregate as AggOr<
886            <L as ComparisonOperand<'a, V, Self>>::Aggregate,
887        >>::Output:
888            AggOr<<H as ComparisonOperand<'a, V, Self>>::Aggregate>,
889    {
890        between(self, low, high)
891    }
892
893    /// NOT BETWEEN comparison.
894    ///
895    /// Checks if the value is NOT between low and high.
896    ///
897    /// ```rust
898    /// # let _ = r####"
899    /// users.age.not_between(0, 17)  // ("users"."age" NOT BETWEEN 0 AND 17)
900    /// # "####;
901    /// ```
902    #[allow(clippy::type_complexity)]
903    fn not_between<L, H>(
904        self,
905        low: L,
906        high: H,
907    ) -> SQLExpr<
908        'a,
909        V,
910        <V::DialectMarker as DialectTypes>::Bool,
911        NonNull,
912        <<Self::Aggregate as AggOr<
913            <L as ComparisonOperand<'a, V, Self>>::Aggregate,
914        >>::Output as AggOr<
915            <H as ComparisonOperand<'a, V, Self>>::Aggregate,
916        >>::Output,
917    >
918    where
919        L: ComparisonOperand<'a, V, Self>,
920        H: ComparisonOperand<'a, V, Self>,
921        Self::SQLType: Compatible<<L as ComparisonOperand<'a, V, Self>>::SQLType>,
922        Self::SQLType: Compatible<<H as ComparisonOperand<'a, V, Self>>::SQLType>,
923        Self::Aggregate:
924            AggOr<<L as ComparisonOperand<'a, V, Self>>::Aggregate>,
925        <Self::Aggregate as AggOr<
926            <L as ComparisonOperand<'a, V, Self>>::Aggregate,
927        >>::Output:
928            AggOr<<H as ComparisonOperand<'a, V, Self>>::Aggregate>,
929    {
930        not_between(self, low, high)
931    }
932
933    /// IN array check.
934    ///
935    /// Checks if the value is in the provided array.
936    ///
937    /// ```rust
938    /// # let _ = r####"
939    /// users.role.in_array([Role::Admin, Role::Moderator])
940    /// // "users"."role" IN ('admin', 'moderator')
941    /// # "####;
942    /// ```
943    fn in_array<I, R>(
944        self,
945        values: I,
946    ) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, Self::Aggregate>
947    where
948        I: IntoIterator<Item = R>,
949        R: Expr<'a, V>,
950        Self::SQLType: Compatible<R::SQLType>,
951    {
952        crate::expr::in_array(self, values)
953    }
954
955    /// NOT IN array check.
956    ///
957    /// Checks if the value is NOT in the provided array.
958    ///
959    /// ```rust
960    /// # let _ = r####"
961    /// users.role.not_in_array([Role::Banned, Role::Suspended])
962    /// // "users"."role" NOT IN ('banned', 'suspended')
963    /// # "####;
964    /// ```
965    fn not_in_array<I, R>(
966        self,
967        values: I,
968    ) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, Self::Aggregate>
969    where
970        I: IntoIterator<Item = R>,
971        R: Expr<'a, V>,
972        Self::SQLType: Compatible<R::SQLType>,
973    {
974        crate::expr::not_in_array(self, values)
975    }
976
977    /// IN subquery check.
978    fn in_subquery<S>(
979        self,
980        subquery: S,
981    ) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, Self::Aggregate>
982    where
983        S: Expr<'a, V>,
984        Self::SQLType: Compatible<S::SQLType>,
985    {
986        crate::expr::in_subquery(self, subquery)
987    }
988
989    /// NOT IN subquery check.
990    fn not_in_subquery<S>(
991        self,
992        subquery: S,
993    ) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, Self::Aggregate>
994    where
995        S: Expr<'a, V>,
996        Self::SQLType: Compatible<S::SQLType>,
997    {
998        crate::expr::not_in_subquery(self, subquery)
999    }
1000
1001    /// IS DISTINCT FROM - NULL-safe inequality comparison.
1002    ///
1003    /// ```rust
1004    /// # let _ = r####"
1005    /// users.status.is_distinct_from("active")
1006    /// // "users"."status" IS DISTINCT FROM 'active'
1007    /// # "####;
1008    /// ```
1009    #[allow(clippy::type_complexity, clippy::wrong_self_convention)]
1010    fn is_distinct_from<R>(
1011        self,
1012        other: R,
1013    ) -> SQLExpr<
1014        'a,
1015        V,
1016        <V::DialectMarker as DialectTypes>::Bool,
1017        NonNull,
1018        <Self::Aggregate as AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>>::Output,
1019    >
1020    where
1021        R: ComparisonOperand<'a, V, Self>,
1022        Self::SQLType: Compatible<<R as ComparisonOperand<'a, V, Self>>::SQLType>,
1023        Self::Aggregate: AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>,
1024    {
1025        is_distinct_from(self, other)
1026    }
1027
1028    /// IS NOT DISTINCT FROM - NULL-safe equality comparison.
1029    ///
1030    /// ```rust
1031    /// # let _ = r####"
1032    /// users.status.is_not_distinct_from("active")
1033    /// // "users"."status" IS NOT DISTINCT FROM 'active'
1034    /// # "####;
1035    /// ```
1036    #[allow(clippy::type_complexity, clippy::wrong_self_convention)]
1037    fn is_not_distinct_from<R>(
1038        self,
1039        other: R,
1040    ) -> SQLExpr<
1041        'a,
1042        V,
1043        <V::DialectMarker as DialectTypes>::Bool,
1044        NonNull,
1045        <Self::Aggregate as AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>>::Output,
1046    >
1047    where
1048        R: ComparisonOperand<'a, V, Self>,
1049        Self::SQLType: Compatible<<R as ComparisonOperand<'a, V, Self>>::SQLType>,
1050        Self::Aggregate: AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>,
1051    {
1052        is_not_distinct_from(self, other)
1053    }
1054
1055    /// IS TRUE - boolean test that handles NULL.
1056    ///
1057    /// ```rust
1058    /// # let _ = r####"
1059    /// users.is_active.is_true()
1060    /// // "users"."is_active" IS TRUE
1061    /// # "####;
1062    /// ```
1063    #[allow(clippy::wrong_self_convention)]
1064    fn is_true(
1065        self,
1066    ) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, Self::Aggregate> {
1067        is_true(self)
1068    }
1069
1070    /// IS FALSE - boolean test that handles NULL.
1071    ///
1072    /// ```rust
1073    /// # let _ = r####"
1074    /// users.is_active.is_false()
1075    /// // "users"."is_active" IS FALSE
1076    /// # "####;
1077    /// ```
1078    #[allow(clippy::wrong_self_convention)]
1079    fn is_false(
1080        self,
1081    ) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, Self::Aggregate> {
1082        is_false(self)
1083    }
1084}
1085
1086/// Blanket implementation for all `Expr` types.
1087impl<'a, V: SQLParam, E: Expr<'a, V>> ExprExt<'a, V> for E {}