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::{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/// SQLite and PostgreSQL render `IS DISTINCT FROM`; MySQL renders the inverse
495/// of its null-safe equality operator, `NOT (left <=> right)`.
496#[allow(clippy::type_complexity)]
497pub fn is_distinct_from<'a, V, L, R>(
498    left: L,
499    right: R,
500) -> SQLExpr<
501    'a,
502    V,
503    <V::DialectMarker as DialectTypes>::Bool,
504    NonNull,
505    <L::Aggregate as AggOr<<R as ComparisonOperand<'a, V, L>>::Aggregate>>::Output,
506>
507where
508    V: SQLParam + 'a,
509    L: Expr<'a, V>,
510    R: ComparisonOperand<'a, V, L>,
511    L::SQLType: Compatible<<R as ComparisonOperand<'a, V, L>>::SQLType>,
512    L::Aggregate: AggOr<<R as ComparisonOperand<'a, V, L>>::Aggregate>,
513{
514    let left = operand_sql(left);
515    let right = ComparisonOperand::into_comparison_sql(right);
516    let sql = match V::DIALECT {
517        Dialect::MySQL => SQL::from(Token::NOT)
518            .push(Token::LPAREN)
519            .append(left)
520            .append(SQL::raw("<=>"))
521            .append(right)
522            .push(Token::RPAREN),
523        Dialect::SQLite | Dialect::PostgreSQL => left
524            .push(Token::IS)
525            .push(Token::DISTINCT)
526            .push(Token::FROM)
527            .append(right),
528    };
529    SQLExpr::new(sql)
530}
531
532/// IS NOT DISTINCT FROM - NULL-safe equality comparison.
533///
534/// Unlike `=`, this treats NULL as a comparable value:
535/// - `NULL IS NOT DISTINCT FROM NULL` → true
536/// - `NULL IS NOT DISTINCT FROM 5` → false
537///
538/// SQLite and PostgreSQL render `IS NOT DISTINCT FROM`; MySQL renders its
539/// null-safe equality operator, `<=>`.
540#[allow(clippy::type_complexity)]
541pub fn is_not_distinct_from<'a, V, L, R>(
542    left: L,
543    right: R,
544) -> SQLExpr<
545    'a,
546    V,
547    <V::DialectMarker as DialectTypes>::Bool,
548    NonNull,
549    <L::Aggregate as AggOr<<R as ComparisonOperand<'a, V, L>>::Aggregate>>::Output,
550>
551where
552    V: SQLParam + 'a,
553    L: Expr<'a, V>,
554    R: ComparisonOperand<'a, V, L>,
555    L::SQLType: Compatible<<R as ComparisonOperand<'a, V, L>>::SQLType>,
556    L::Aggregate: AggOr<<R as ComparisonOperand<'a, V, L>>::Aggregate>,
557{
558    let left = operand_sql(left);
559    let right = ComparisonOperand::into_comparison_sql(right);
560    let sql = match V::DIALECT {
561        Dialect::MySQL => left.append(SQL::raw("<=>")).append(right),
562        Dialect::SQLite | Dialect::PostgreSQL => left
563            .push(Token::IS)
564            .push(Token::NOT)
565            .push(Token::DISTINCT)
566            .push(Token::FROM)
567            .append(right),
568    };
569    SQLExpr::new(sql)
570}
571
572// =============================================================================
573// Boolean Testing
574// =============================================================================
575
576/// IS TRUE - tests if a boolean expression is true.
577///
578/// Unlike `= TRUE`, this handles NULL correctly:
579/// - `TRUE IS TRUE` → true
580/// - `FALSE IS TRUE` → false
581/// - `NULL IS TRUE` → false (not NULL!)
582pub fn is_true<'a, V, E>(
583    expr: E,
584) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, E::Aggregate>
585where
586    V: SQLParam + 'a,
587    E: Expr<'a, V>,
588{
589    SQLExpr::new(operand_sql(expr).push(Token::IS).append(SQL::raw("TRUE")))
590}
591
592/// IS FALSE - tests if a boolean expression is false.
593///
594/// Unlike `= FALSE`, this handles NULL correctly:
595/// - `FALSE IS FALSE` → true
596/// - `TRUE IS FALSE` → false
597/// - `NULL IS FALSE` → false (not NULL!)
598pub fn is_false<'a, V, E>(
599    expr: E,
600) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, E::Aggregate>
601where
602    V: SQLParam + 'a,
603    E: Expr<'a, V>,
604{
605    SQLExpr::new(operand_sql(expr).push(Token::IS).append(SQL::raw("FALSE")))
606}
607
608// =============================================================================
609// Method-based Comparison API (Extension Trait)
610// =============================================================================
611
612/// Extension trait providing method-based comparisons for any `Expr` type.
613///
614/// This trait is blanket-implemented for all types implementing `Expr`,
615/// allowing method syntax on columns, literals, and expressions:
616///
617/// ```rust
618/// # let _ = r####"
619/// // Works on columns directly
620/// users.id.eq(42)
621/// users.age.gt(18)
622///
623/// // Chain with operators
624/// users.id.eq(42) & users.age.gt(18)
625/// # "####;
626/// ```
627pub trait ExprExt<'a, V: SQLParam>: Expr<'a, V> + Sized {
628    /// Equality comparison (`=`).
629    ///
630    /// ```rust
631    /// # let _ = r####"
632    /// users.id.eq(42)  // "users"."id" = 42
633    /// # "####;
634    /// ```
635    #[allow(clippy::type_complexity)]
636    fn eq<R>(
637        self,
638        other: R,
639    ) -> SQLExpr<
640        'a,
641        V,
642        <V::DialectMarker as DialectTypes>::Bool,
643        NonNull,
644        <Self::Aggregate as AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>>::Output,
645    >
646    where
647        R: ComparisonOperand<'a, V, Self>,
648        Self::SQLType: Compatible<<R as ComparisonOperand<'a, V, Self>>::SQLType>,
649        Self::Aggregate: AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>,
650    {
651        eq(self, other)
652    }
653
654    /// Inequality comparison (`<>`).
655    ///
656    /// ```rust
657    /// # let _ = r####"
658    /// users.id.ne(42)  // "users"."id" <> 42
659    /// # "####;
660    /// ```
661    #[allow(clippy::type_complexity)]
662    fn ne<R>(
663        self,
664        other: R,
665    ) -> SQLExpr<
666        'a,
667        V,
668        <V::DialectMarker as DialectTypes>::Bool,
669        NonNull,
670        <Self::Aggregate as AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>>::Output,
671    >
672    where
673        R: ComparisonOperand<'a, V, Self>,
674        Self::SQLType: Compatible<<R as ComparisonOperand<'a, V, Self>>::SQLType>,
675        Self::Aggregate: AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>,
676    {
677        ne(self, other)
678    }
679
680    /// Greater-than comparison (`>`).
681    ///
682    /// ```rust
683    /// # let _ = r####"
684    /// users.age.gt(18)  // "users"."age" > 18
685    /// # "####;
686    /// ```
687    #[allow(clippy::type_complexity)]
688    fn gt<R>(
689        self,
690        other: R,
691    ) -> SQLExpr<
692        'a,
693        V,
694        <V::DialectMarker as DialectTypes>::Bool,
695        NonNull,
696        <Self::Aggregate as AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>>::Output,
697    >
698    where
699        R: ComparisonOperand<'a, V, Self>,
700        Self::SQLType: Compatible<<R as ComparisonOperand<'a, V, Self>>::SQLType>,
701        Self::Aggregate: AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>,
702    {
703        gt(self, other)
704    }
705
706    /// Greater-than-or-equal comparison (`>=`).
707    ///
708    /// ```rust
709    /// # let _ = r####"
710    /// users.age.ge(18)  // "users"."age" >= 18
711    /// # "####;
712    /// ```
713    #[allow(clippy::type_complexity)]
714    fn ge<R>(
715        self,
716        other: R,
717    ) -> SQLExpr<
718        'a,
719        V,
720        <V::DialectMarker as DialectTypes>::Bool,
721        NonNull,
722        <Self::Aggregate as AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>>::Output,
723    >
724    where
725        R: ComparisonOperand<'a, V, Self>,
726        Self::SQLType: Compatible<<R as ComparisonOperand<'a, V, Self>>::SQLType>,
727        Self::Aggregate: AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>,
728    {
729        gte(self, other)
730    }
731
732    /// Less-than comparison (`<`).
733    ///
734    /// ```rust
735    /// # let _ = r####"
736    /// users.age.lt(65)  // "users"."age" < 65
737    /// # "####;
738    /// ```
739    #[allow(clippy::type_complexity)]
740    fn lt<R>(
741        self,
742        other: R,
743    ) -> SQLExpr<
744        'a,
745        V,
746        <V::DialectMarker as DialectTypes>::Bool,
747        NonNull,
748        <Self::Aggregate as AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>>::Output,
749    >
750    where
751        R: ComparisonOperand<'a, V, Self>,
752        Self::SQLType: Compatible<<R as ComparisonOperand<'a, V, Self>>::SQLType>,
753        Self::Aggregate: AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>,
754    {
755        lt(self, other)
756    }
757
758    /// Less-than-or-equal comparison (`<=`).
759    ///
760    /// ```rust
761    /// # let _ = r####"
762    /// users.age.le(65)  // "users"."age" <= 65
763    /// # "####;
764    /// ```
765    #[allow(clippy::type_complexity)]
766    fn le<R>(
767        self,
768        other: R,
769    ) -> SQLExpr<
770        'a,
771        V,
772        <V::DialectMarker as DialectTypes>::Bool,
773        NonNull,
774        <Self::Aggregate as AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>>::Output,
775    >
776    where
777        R: ComparisonOperand<'a, V, Self>,
778        Self::SQLType: Compatible<<R as ComparisonOperand<'a, V, Self>>::SQLType>,
779        Self::Aggregate: AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>,
780    {
781        lte(self, other)
782    }
783
784    /// LIKE pattern matching.
785    ///
786    /// ```rust
787    /// # let _ = r####"
788    /// users.name.like("%Alice%")  // "users"."name" LIKE '%Alice%'
789    /// # "####;
790    /// ```
791    #[allow(clippy::type_complexity)]
792    fn like<R>(
793        self,
794        pattern: R,
795    ) -> SQLExpr<
796        'a,
797        V,
798        <V::DialectMarker as DialectTypes>::Bool,
799        NonNull,
800        <Self::Aggregate as AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>>::Output,
801    >
802    where
803        R: ComparisonOperand<'a, V, Self>,
804        Self::SQLType: Compatible<<R as ComparisonOperand<'a, V, Self>>::SQLType>,
805        Self::SQLType: Textual,
806        <R as ComparisonOperand<'a, V, Self>>::SQLType: Textual,
807        Self::Aggregate: AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>,
808    {
809        like(self, pattern)
810    }
811
812    /// NOT LIKE pattern matching.
813    ///
814    /// ```rust
815    /// # let _ = r####"
816    /// users.name.not_like("%Bot%")  // "users"."name" NOT LIKE '%Bot%'
817    /// # "####;
818    /// ```
819    #[allow(clippy::type_complexity)]
820    fn not_like<R>(
821        self,
822        pattern: R,
823    ) -> SQLExpr<
824        'a,
825        V,
826        <V::DialectMarker as DialectTypes>::Bool,
827        NonNull,
828        <Self::Aggregate as AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>>::Output,
829    >
830    where
831        R: ComparisonOperand<'a, V, Self>,
832        Self::SQLType: Compatible<<R as ComparisonOperand<'a, V, Self>>::SQLType>,
833        Self::SQLType: Textual,
834        <R as ComparisonOperand<'a, V, Self>>::SQLType: Textual,
835        Self::Aggregate: AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>,
836    {
837        not_like(self, pattern)
838    }
839
840    /// IS NULL check.
841    ///
842    /// ```rust
843    /// # let _ = r####"
844    /// users.deleted_at.is_null()  // "users"."deleted_at" IS NULL
845    /// # "####;
846    /// ```
847    #[allow(clippy::wrong_self_convention)]
848    fn is_null(
849        self,
850    ) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, Self::Aggregate> {
851        is_null(self)
852    }
853
854    /// IS NOT NULL check.
855    ///
856    /// ```rust
857    /// # let _ = r####"
858    /// users.email.is_not_null()  // "users"."email" IS NOT NULL
859    /// # "####;
860    /// ```
861    #[allow(clippy::wrong_self_convention)]
862    fn is_not_null(
863        self,
864    ) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, Self::Aggregate> {
865        is_not_null(self)
866    }
867
868    /// BETWEEN comparison.
869    ///
870    /// Checks if the value is between low and high (inclusive).
871    ///
872    /// ```rust
873    /// # let _ = r####"
874    /// users.age.between(18, 65)  // ("users"."age" BETWEEN 18 AND 65)
875    /// # "####;
876    /// ```
877    #[allow(clippy::type_complexity)]
878    fn between<L, H>(
879        self,
880        low: L,
881        high: H,
882    ) -> SQLExpr<
883        'a,
884        V,
885        <V::DialectMarker as DialectTypes>::Bool,
886        NonNull,
887        <<Self::Aggregate as AggOr<
888            <L as ComparisonOperand<'a, V, Self>>::Aggregate,
889        >>::Output as AggOr<
890            <H as ComparisonOperand<'a, V, Self>>::Aggregate,
891        >>::Output,
892    >
893    where
894        L: ComparisonOperand<'a, V, Self>,
895        H: ComparisonOperand<'a, V, Self>,
896        Self::SQLType: Compatible<<L as ComparisonOperand<'a, V, Self>>::SQLType>,
897        Self::SQLType: Compatible<<H as ComparisonOperand<'a, V, Self>>::SQLType>,
898        Self::Aggregate:
899            AggOr<<L as ComparisonOperand<'a, V, Self>>::Aggregate>,
900        <Self::Aggregate as AggOr<
901            <L as ComparisonOperand<'a, V, Self>>::Aggregate,
902        >>::Output:
903            AggOr<<H as ComparisonOperand<'a, V, Self>>::Aggregate>,
904{
905        between(self, low, high)
906    }
907
908    /// NOT BETWEEN comparison.
909    ///
910    /// Checks if the value is NOT between low and high.
911    ///
912    /// ```rust
913    /// # let _ = r####"
914    /// users.age.not_between(0, 17)  // ("users"."age" NOT BETWEEN 0 AND 17)
915    /// # "####;
916    /// ```
917    #[allow(clippy::type_complexity)]
918    fn not_between<L, H>(
919        self,
920        low: L,
921        high: H,
922    ) -> SQLExpr<
923        'a,
924        V,
925        <V::DialectMarker as DialectTypes>::Bool,
926        NonNull,
927        <<Self::Aggregate as AggOr<
928            <L as ComparisonOperand<'a, V, Self>>::Aggregate,
929        >>::Output as AggOr<
930            <H as ComparisonOperand<'a, V, Self>>::Aggregate,
931        >>::Output,
932    >
933    where
934        L: ComparisonOperand<'a, V, Self>,
935        H: ComparisonOperand<'a, V, Self>,
936        Self::SQLType: Compatible<<L as ComparisonOperand<'a, V, Self>>::SQLType>,
937        Self::SQLType: Compatible<<H as ComparisonOperand<'a, V, Self>>::SQLType>,
938        Self::Aggregate:
939            AggOr<<L as ComparisonOperand<'a, V, Self>>::Aggregate>,
940        <Self::Aggregate as AggOr<
941            <L as ComparisonOperand<'a, V, Self>>::Aggregate,
942        >>::Output:
943            AggOr<<H as ComparisonOperand<'a, V, Self>>::Aggregate>,
944{
945        not_between(self, low, high)
946    }
947
948    /// IN array check.
949    ///
950    /// Checks if the value is in the provided array.
951    ///
952    /// ```rust
953    /// # let _ = r####"
954    /// users.role.in_array([Role::Admin, Role::Moderator])
955    /// // "users"."role" IN ('admin', 'moderator')
956    /// # "####;
957    /// ```
958    fn in_array<I, R>(
959        self,
960        values: I,
961    ) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, Self::Aggregate>
962    where
963        I: IntoIterator<Item = R>,
964        R: Expr<'a, V>,
965        Self::SQLType: Compatible<R::SQLType>,
966    {
967        crate::expr::in_array(self, values)
968    }
969
970    /// NOT IN array check.
971    ///
972    /// Checks if the value is NOT in the provided array.
973    ///
974    /// ```rust
975    /// # let _ = r####"
976    /// users.role.not_in_array([Role::Banned, Role::Suspended])
977    /// // "users"."role" NOT IN ('banned', 'suspended')
978    /// # "####;
979    /// ```
980    fn not_in_array<I, R>(
981        self,
982        values: I,
983    ) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, Self::Aggregate>
984    where
985        I: IntoIterator<Item = R>,
986        R: Expr<'a, V>,
987        Self::SQLType: Compatible<R::SQLType>,
988    {
989        crate::expr::not_in_array(self, values)
990    }
991
992    /// IN subquery check.
993    fn in_subquery<S>(
994        self,
995        subquery: S,
996    ) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, Self::Aggregate>
997    where
998        S: Expr<'a, V>,
999        Self::SQLType: Compatible<S::SQLType> + Compatible<Self::SQLType>,
1000    {
1001        crate::expr::in_subquery(self, subquery)
1002    }
1003
1004    /// NOT IN subquery check.
1005    fn not_in_subquery<S>(
1006        self,
1007        subquery: S,
1008    ) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, Self::Aggregate>
1009    where
1010        S: Expr<'a, V>,
1011        Self::SQLType: Compatible<S::SQLType> + Compatible<Self::SQLType>,
1012    {
1013        crate::expr::not_in_subquery(self, subquery)
1014    }
1015
1016    /// IS DISTINCT FROM - NULL-safe inequality comparison.
1017    ///
1018    /// ```rust
1019    /// # let _ = r####"
1020    /// users.status.is_distinct_from("active")
1021    /// // "users"."status" IS DISTINCT FROM 'active'
1022    /// # "####;
1023    /// ```
1024    #[allow(clippy::type_complexity, clippy::wrong_self_convention)]
1025    fn is_distinct_from<R>(
1026        self,
1027        other: R,
1028    ) -> SQLExpr<
1029        'a,
1030        V,
1031        <V::DialectMarker as DialectTypes>::Bool,
1032        NonNull,
1033        <Self::Aggregate as AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>>::Output,
1034    >
1035    where
1036        R: ComparisonOperand<'a, V, Self>,
1037        Self::SQLType: Compatible<<R as ComparisonOperand<'a, V, Self>>::SQLType>,
1038        Self::Aggregate: AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>,
1039    {
1040        is_distinct_from(self, other)
1041    }
1042
1043    /// IS NOT DISTINCT FROM - NULL-safe equality comparison.
1044    ///
1045    /// ```rust
1046    /// # let _ = r####"
1047    /// users.status.is_not_distinct_from("active")
1048    /// // "users"."status" IS NOT DISTINCT FROM 'active'
1049    /// # "####;
1050    /// ```
1051    #[allow(clippy::type_complexity, clippy::wrong_self_convention)]
1052    fn is_not_distinct_from<R>(
1053        self,
1054        other: R,
1055    ) -> SQLExpr<
1056        'a,
1057        V,
1058        <V::DialectMarker as DialectTypes>::Bool,
1059        NonNull,
1060        <Self::Aggregate as AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>>::Output,
1061    >
1062    where
1063        R: ComparisonOperand<'a, V, Self>,
1064        Self::SQLType: Compatible<<R as ComparisonOperand<'a, V, Self>>::SQLType>,
1065        Self::Aggregate: AggOr<<R as ComparisonOperand<'a, V, Self>>::Aggregate>,
1066    {
1067        is_not_distinct_from(self, other)
1068    }
1069
1070    /// IS TRUE - boolean test that handles NULL.
1071    ///
1072    /// ```rust
1073    /// # let _ = r####"
1074    /// users.is_active.is_true()
1075    /// // "users"."is_active" IS TRUE
1076    /// # "####;
1077    /// ```
1078    #[allow(clippy::wrong_self_convention)]
1079    fn is_true(
1080        self,
1081    ) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, Self::Aggregate> {
1082        is_true(self)
1083    }
1084
1085    /// IS FALSE - boolean test that handles NULL.
1086    ///
1087    /// ```rust
1088    /// # let _ = r####"
1089    /// users.is_active.is_false()
1090    /// // "users"."is_active" IS FALSE
1091    /// # "####;
1092    /// ```
1093    #[allow(clippy::wrong_self_convention)]
1094    fn is_false(
1095        self,
1096    ) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, Self::Aggregate> {
1097        is_false(self)
1098    }
1099}
1100
1101/// Blanket implementation for all `Expr` types.
1102impl<'a, V: SQLParam, E: Expr<'a, V>> ExprExt<'a, V> for E {}