Skip to main content

sqlmodel_query/
expr.rs

1//! SQL expressions for query building.
2//!
3//! This module provides a type-safe expression system for building
4//! WHERE clauses, ORDER BY, computed columns, and other SQL expressions.
5
6use crate::clause::{OrderBy, OrderDirection};
7use sqlmodel_core::Value;
8
9/// SQL dialect for generating dialect-specific SQL.
10///
11/// Re-exported from `sqlmodel_core` to ensure consistency across the ecosystem.
12pub use sqlmodel_core::Dialect;
13
14/// A SQL expression that can be used in WHERE, HAVING, etc.
15#[derive(Debug, Clone)]
16pub enum Expr {
17    /// Column reference with optional table qualifier
18    Column {
19        /// Optional table name or alias
20        table: Option<String>,
21        /// Column name
22        name: String,
23    },
24
25    /// Literal value
26    Literal(Value),
27
28    /// Explicit placeholder for bound parameters
29    Placeholder(usize),
30
31    /// Binary operation (e.g., a = b, a > b)
32    Binary {
33        left: Box<Expr>,
34        op: BinaryOp,
35        right: Box<Expr>,
36    },
37
38    /// Unary operation (e.g., NOT a, -a)
39    Unary { op: UnaryOp, expr: Box<Expr> },
40
41    /// Function call (e.g., COUNT(*), UPPER(name))
42    Function { name: String, args: Vec<Expr> },
43
44    /// CASE WHEN ... THEN ... ELSE ... END
45    Case {
46        /// List of (condition, result) pairs
47        when_clauses: Vec<(Expr, Expr)>,
48        /// Optional ELSE clause
49        else_clause: Option<Box<Expr>>,
50    },
51
52    /// IN expression
53    In {
54        expr: Box<Expr>,
55        values: Vec<Expr>,
56        negated: bool,
57    },
58
59    /// BETWEEN expression
60    Between {
61        expr: Box<Expr>,
62        low: Box<Expr>,
63        high: Box<Expr>,
64        negated: bool,
65    },
66
67    /// IS NULL / IS NOT NULL
68    IsNull { expr: Box<Expr>, negated: bool },
69
70    /// IS DISTINCT FROM / IS NOT DISTINCT FROM (NULL-safe comparison)
71    IsDistinctFrom {
72        left: Box<Expr>,
73        right: Box<Expr>,
74        negated: bool,
75    },
76
77    /// CAST(expr AS type)
78    Cast { expr: Box<Expr>, type_name: String },
79
80    /// LIKE / NOT LIKE pattern
81    Like {
82        expr: Box<Expr>,
83        pattern: String,
84        negated: bool,
85        case_insensitive: bool,
86    },
87
88    /// Subquery (stores the SQL string)
89    Subquery(String),
90
91    /// EXISTS (subquery) / NOT EXISTS (subquery)
92    ///
93    /// Used for subquery existence checks in WHERE clauses.
94    Exists {
95        /// The subquery SQL string
96        subquery: String,
97        /// Parameters for the subquery
98        params: Vec<Value>,
99        /// Whether this is NOT EXISTS
100        negated: bool,
101    },
102
103    /// Raw SQL fragment (escape hatch)
104    Raw(String),
105
106    /// Parenthesized expression
107    Paren(Box<Expr>),
108
109    /// Special aggregate: COUNT(*)
110    CountStar,
111
112    /// Window function with OVER clause
113    Window {
114        /// The function expression (aggregate or window function)
115        function: Box<Expr>,
116        /// PARTITION BY expressions
117        partition_by: Vec<Expr>,
118        /// ORDER BY clauses within the window
119        order_by: Vec<OrderBy>,
120        /// Frame specification (ROWS or RANGE)
121        frame: Option<WindowFrame>,
122    },
123
124    /// JSON path extraction (returns JSON value)
125    ///
126    /// - PostgreSQL: `expr -> 'path'` or `expr -> path_expr`
127    /// - MySQL: `JSON_EXTRACT(expr, '$.path')`
128    /// - SQLite: `json_extract(expr, '$.path')`
129    JsonExtract {
130        /// The JSON expression to extract from
131        expr: Box<Expr>,
132        /// The path to extract (can be a key name or array index)
133        path: JsonPath,
134    },
135
136    /// JSON path extraction as text (returns text/string)
137    ///
138    /// - PostgreSQL: `expr ->> 'path'`
139    /// - MySQL: `JSON_UNQUOTE(JSON_EXTRACT(expr, '$.path'))`
140    /// - SQLite: `json_extract(expr, '$.path')` (SQLite returns text by default)
141    JsonExtractText {
142        /// The JSON expression to extract from
143        expr: Box<Expr>,
144        /// The path to extract
145        path: JsonPath,
146    },
147
148    /// JSON path extraction with nested path (returns JSON)
149    ///
150    /// - PostgreSQL: `expr #> '{path, to, value}'`
151    /// - MySQL/SQLite: `JSON_EXTRACT(expr, '$.path.to.value')`
152    JsonExtractPath {
153        /// The JSON expression
154        expr: Box<Expr>,
155        /// Nested path segments
156        path: Vec<String>,
157    },
158
159    /// JSON path extraction with nested path as text
160    ///
161    /// - PostgreSQL: `expr #>> '{path, to, value}'`
162    /// - MySQL: `JSON_UNQUOTE(JSON_EXTRACT(expr, '$.path.to.value'))`
163    /// - SQLite: `json_extract(expr, '$.path.to.value')`
164    JsonExtractPathText {
165        /// The JSON expression
166        expr: Box<Expr>,
167        /// Nested path segments
168        path: Vec<String>,
169    },
170
171    /// JSON containment check (left contains right)
172    ///
173    /// - PostgreSQL: `expr @> other` (JSONB only)
174    /// - MySQL: `JSON_CONTAINS(expr, other)`
175    /// - SQLite: Not directly supported (requires json_each workaround)
176    JsonContains {
177        /// The JSON expression to check
178        expr: Box<Expr>,
179        /// The JSON value to check for
180        other: Box<Expr>,
181    },
182
183    /// JSON contained-by check (left is contained by right)
184    ///
185    /// - PostgreSQL: `expr <@ other` (JSONB only)
186    /// - MySQL: `JSON_CONTAINS(other, expr)`
187    /// - SQLite: Not directly supported
188    JsonContainedBy {
189        /// The JSON expression to check
190        expr: Box<Expr>,
191        /// The containing JSON value
192        other: Box<Expr>,
193    },
194
195    /// JSON key existence check
196    ///
197    /// - PostgreSQL: `expr ? 'key'` (JSONB only)
198    /// - MySQL: `JSON_CONTAINS_PATH(expr, 'one', '$.key')`
199    /// - SQLite: `json_type(expr, '$.key') IS NOT NULL`
200    JsonHasKey {
201        /// The JSON expression
202        expr: Box<Expr>,
203        /// The key to check for
204        key: String,
205    },
206
207    /// JSON any key existence (has any of the keys)
208    ///
209    /// - PostgreSQL: `expr ?| array['key1', 'key2']` (JSONB only)
210    /// - MySQL: `JSON_CONTAINS_PATH(expr, 'one', '$.key1', '$.key2')`
211    /// - SQLite: Requires OR of json_type checks
212    JsonHasAnyKey {
213        /// The JSON expression
214        expr: Box<Expr>,
215        /// The keys to check for
216        keys: Vec<String>,
217    },
218
219    /// JSON all keys existence (has all of the keys)
220    ///
221    /// - PostgreSQL: `expr ?& array['key1', 'key2']` (JSONB only)
222    /// - MySQL: `JSON_CONTAINS_PATH(expr, 'all', '$.key1', '$.key2')`
223    /// - SQLite: Requires AND of json_type checks
224    JsonHasAllKeys {
225        /// The JSON expression
226        expr: Box<Expr>,
227        /// The keys to check for
228        keys: Vec<String>,
229    },
230
231    /// JSON array length
232    ///
233    /// - PostgreSQL: `jsonb_array_length(expr)`
234    /// - MySQL: `JSON_LENGTH(expr)`
235    /// - SQLite: `json_array_length(expr)`
236    JsonArrayLength {
237        /// The JSON array expression
238        expr: Box<Expr>,
239    },
240
241    /// JSON typeof (returns the type of the JSON value)
242    ///
243    /// - PostgreSQL: `jsonb_typeof(expr)`
244    /// - MySQL: `JSON_TYPE(expr)`
245    /// - SQLite: `json_type(expr)`
246    JsonTypeof {
247        /// The JSON expression
248        expr: Box<Expr>,
249    },
250}
251
252/// JSON path segment for extraction operations.
253#[derive(Debug, Clone)]
254pub enum JsonPath {
255    /// Object key access (e.g., `-> 'name'`)
256    Key(String),
257    /// Array index access (e.g., `-> 0`)
258    Index(i64),
259}
260
261/// Binary operators.
262#[derive(Debug, Clone, Copy, PartialEq, Eq)]
263pub enum BinaryOp {
264    // Comparison
265    /// Equal (=)
266    Eq,
267    /// Not equal (<>)
268    Ne,
269    /// Less than (<)
270    Lt,
271    /// Less than or equal (<=)
272    Le,
273    /// Greater than (>)
274    Gt,
275    /// Greater than or equal (>=)
276    Ge,
277
278    // Logical
279    /// Logical AND
280    And,
281    /// Logical OR
282    Or,
283
284    // Arithmetic
285    /// Addition (+)
286    Add,
287    /// Subtraction (-)
288    Sub,
289    /// Multiplication (*)
290    Mul,
291    /// Division (/)
292    Div,
293    /// Modulo (%)
294    Mod,
295
296    // Bitwise
297    /// Bitwise AND (&)
298    BitAnd,
299    /// Bitwise OR (|)
300    BitOr,
301    /// Bitwise XOR (^)
302    BitXor,
303
304    // String
305    /// String concatenation (||)
306    Concat,
307
308    // Array (PostgreSQL)
309    /// Array contains (@>)
310    ArrayContains,
311    /// Array is contained by (<@)
312    ArrayContainedBy,
313    /// Array overlap (&&)
314    ArrayOverlap,
315}
316
317impl BinaryOp {
318    /// Get the SQL representation of this operator.
319    pub const fn as_str(self) -> &'static str {
320        match self {
321            BinaryOp::Eq => "=",
322            BinaryOp::Ne => "<>",
323            BinaryOp::Lt => "<",
324            BinaryOp::Le => "<=",
325            BinaryOp::Gt => ">",
326            BinaryOp::Ge => ">=",
327            BinaryOp::And => "AND",
328            BinaryOp::Or => "OR",
329            BinaryOp::Add => "+",
330            BinaryOp::Sub => "-",
331            BinaryOp::Mul => "*",
332            BinaryOp::Div => "/",
333            BinaryOp::Mod => "%",
334            BinaryOp::BitAnd => "&",
335            BinaryOp::BitOr => "|",
336            BinaryOp::BitXor => "^",
337            BinaryOp::Concat => "||",
338            BinaryOp::ArrayContains => "@>",
339            BinaryOp::ArrayContainedBy => "<@",
340            BinaryOp::ArrayOverlap => "&&",
341        }
342    }
343
344    /// Get the precedence of this operator (higher = binds tighter).
345    pub const fn precedence(self) -> u8 {
346        match self {
347            BinaryOp::Or => 1,
348            BinaryOp::And => 2,
349            BinaryOp::Eq
350            | BinaryOp::Ne
351            | BinaryOp::Lt
352            | BinaryOp::Le
353            | BinaryOp::Gt
354            | BinaryOp::Ge
355            | BinaryOp::ArrayContains
356            | BinaryOp::ArrayContainedBy
357            | BinaryOp::ArrayOverlap => 3,
358            BinaryOp::BitOr => 4,
359            BinaryOp::BitXor => 5,
360            BinaryOp::BitAnd => 6,
361            BinaryOp::Add | BinaryOp::Sub | BinaryOp::Concat => 7,
362            BinaryOp::Mul | BinaryOp::Div | BinaryOp::Mod => 8,
363        }
364    }
365}
366
367/// Unary operators.
368#[derive(Debug, Clone, Copy, PartialEq, Eq)]
369pub enum UnaryOp {
370    Not,
371    Neg,
372    BitwiseNot,
373}
374
375impl UnaryOp {
376    /// Get the SQL representation of this operator.
377    pub const fn as_str(&self) -> &'static str {
378        match self {
379            UnaryOp::Not => "NOT",
380            UnaryOp::Neg => "-",
381            UnaryOp::BitwiseNot => "~",
382        }
383    }
384}
385
386// ==================== Window Frame ====================
387
388/// Window frame specification for OVER clause.
389#[derive(Debug, Clone)]
390pub struct WindowFrame {
391    /// Frame type: ROWS or RANGE
392    pub frame_type: WindowFrameType,
393    /// Frame start bound
394    pub start: WindowFrameBound,
395    /// Frame end bound (if BETWEEN is used)
396    pub end: Option<WindowFrameBound>,
397}
398
399/// Window frame type.
400#[derive(Debug, Clone, Copy, PartialEq, Eq)]
401pub enum WindowFrameType {
402    /// ROWS - physical rows
403    Rows,
404    /// RANGE - logical range based on ORDER BY values
405    Range,
406    /// GROUPS - groups of peer rows (PostgreSQL 11+)
407    Groups,
408}
409
410impl WindowFrameType {
411    /// Get the SQL keyword for this frame type.
412    pub const fn as_str(self) -> &'static str {
413        match self {
414            WindowFrameType::Rows => "ROWS",
415            WindowFrameType::Range => "RANGE",
416            WindowFrameType::Groups => "GROUPS",
417        }
418    }
419}
420
421/// Window frame bound specification.
422#[derive(Debug, Clone)]
423pub enum WindowFrameBound {
424    /// UNBOUNDED PRECEDING
425    UnboundedPreceding,
426    /// UNBOUNDED FOLLOWING
427    UnboundedFollowing,
428    /// CURRENT ROW
429    CurrentRow,
430    /// N PRECEDING
431    Preceding(u64),
432    /// N FOLLOWING
433    Following(u64),
434}
435
436impl WindowFrameBound {
437    /// Generate SQL for this frame bound.
438    pub fn to_sql(&self) -> String {
439        match self {
440            WindowFrameBound::UnboundedPreceding => "UNBOUNDED PRECEDING".to_string(),
441            WindowFrameBound::UnboundedFollowing => "UNBOUNDED FOLLOWING".to_string(),
442            WindowFrameBound::CurrentRow => "CURRENT ROW".to_string(),
443            WindowFrameBound::Preceding(n) => format!("{n} PRECEDING"),
444            WindowFrameBound::Following(n) => format!("{n} FOLLOWING"),
445        }
446    }
447}
448
449impl Expr {
450    // ==================== Constructors ====================
451
452    /// Create a column reference expression.
453    pub fn col(name: impl Into<String>) -> Self {
454        Expr::Column {
455            table: None,
456            name: name.into(),
457        }
458    }
459
460    /// Create a qualified column reference (table.column).
461    pub fn qualified(table: impl Into<String>, column: impl Into<String>) -> Self {
462        Expr::Column {
463            table: Some(table.into()),
464            name: column.into(),
465        }
466    }
467
468    /// Create a literal value expression.
469    pub fn lit(value: impl Into<Value>) -> Self {
470        Expr::Literal(value.into())
471    }
472
473    /// Create a NULL literal.
474    pub fn null() -> Self {
475        Expr::Literal(Value::Null)
476    }
477
478    /// Create a raw SQL expression (escape hatch).
479    pub fn raw(sql: impl Into<String>) -> Self {
480        Expr::Raw(sql.into())
481    }
482
483    /// Create a placeholder for bound parameters.
484    pub fn placeholder(index: usize) -> Self {
485        Expr::Placeholder(index)
486    }
487
488    // ==================== Comparison Operators ====================
489
490    /// Equal to (=)
491    pub fn eq(self, other: impl Into<Expr>) -> Self {
492        Expr::Binary {
493            left: Box::new(self),
494            op: BinaryOp::Eq,
495            right: Box::new(other.into()),
496        }
497    }
498
499    /// Not equal to (<>)
500    pub fn ne(self, other: impl Into<Expr>) -> Self {
501        Expr::Binary {
502            left: Box::new(self),
503            op: BinaryOp::Ne,
504            right: Box::new(other.into()),
505        }
506    }
507
508    /// Less than (<)
509    pub fn lt(self, other: impl Into<Expr>) -> Self {
510        Expr::Binary {
511            left: Box::new(self),
512            op: BinaryOp::Lt,
513            right: Box::new(other.into()),
514        }
515    }
516
517    /// Less than or equal to (<=)
518    pub fn le(self, other: impl Into<Expr>) -> Self {
519        Expr::Binary {
520            left: Box::new(self),
521            op: BinaryOp::Le,
522            right: Box::new(other.into()),
523        }
524    }
525
526    /// Greater than (>)
527    pub fn gt(self, other: impl Into<Expr>) -> Self {
528        Expr::Binary {
529            left: Box::new(self),
530            op: BinaryOp::Gt,
531            right: Box::new(other.into()),
532        }
533    }
534
535    /// Greater than or equal to (>=)
536    pub fn ge(self, other: impl Into<Expr>) -> Self {
537        Expr::Binary {
538            left: Box::new(self),
539            op: BinaryOp::Ge,
540            right: Box::new(other.into()),
541        }
542    }
543
544    // ==================== Logical Operators ====================
545
546    /// Logical AND
547    pub fn and(self, other: impl Into<Expr>) -> Self {
548        Expr::Binary {
549            left: Box::new(self),
550            op: BinaryOp::And,
551            right: Box::new(other.into()),
552        }
553    }
554
555    /// Logical OR
556    pub fn or(self, other: impl Into<Expr>) -> Self {
557        Expr::Binary {
558            left: Box::new(self),
559            op: BinaryOp::Or,
560            right: Box::new(other.into()),
561        }
562    }
563
564    /// Logical NOT
565    pub fn not(self) -> Self {
566        Expr::Unary {
567            op: UnaryOp::Not,
568            expr: Box::new(self),
569        }
570    }
571
572    // ==================== Null Checks ====================
573
574    /// IS NULL
575    pub fn is_null(self) -> Self {
576        Expr::IsNull {
577            expr: Box::new(self),
578            negated: false,
579        }
580    }
581
582    /// IS NOT NULL
583    pub fn is_not_null(self) -> Self {
584        Expr::IsNull {
585            expr: Box::new(self),
586            negated: true,
587        }
588    }
589
590    /// IS DISTINCT FROM (NULL-safe comparison: returns TRUE/FALSE, never NULL)
591    ///
592    /// Unlike `!=`, this returns TRUE when comparing NULL to a non-NULL value,
593    /// and FALSE when comparing NULL to NULL.
594    pub fn is_distinct_from(self, other: impl Into<Expr>) -> Self {
595        Expr::IsDistinctFrom {
596            left: Box::new(self),
597            right: Box::new(other.into()),
598            negated: false,
599        }
600    }
601
602    /// IS NOT DISTINCT FROM (NULL-safe equality: returns TRUE/FALSE, never NULL)
603    ///
604    /// Unlike `=`, this returns TRUE when comparing NULL to NULL,
605    /// and FALSE when comparing NULL to a non-NULL value.
606    pub fn is_not_distinct_from(self, other: impl Into<Expr>) -> Self {
607        Expr::IsDistinctFrom {
608            left: Box::new(self),
609            right: Box::new(other.into()),
610            negated: true,
611        }
612    }
613
614    // ==================== Type Casting ====================
615
616    /// CAST expression to a specific SQL type.
617    ///
618    /// # Example
619    /// ```ignore
620    /// Expr::col("price").cast("DECIMAL(10, 2)")
621    /// // Generates: CAST("price" AS DECIMAL(10, 2))
622    /// ```
623    pub fn cast(self, type_name: impl Into<String>) -> Self {
624        Expr::Cast {
625            expr: Box::new(self),
626            type_name: type_name.into(),
627        }
628    }
629
630    // ==================== Pattern Matching ====================
631
632    /// LIKE pattern match
633    pub fn like(self, pattern: impl Into<String>) -> Self {
634        Expr::Like {
635            expr: Box::new(self),
636            pattern: pattern.into(),
637            negated: false,
638            case_insensitive: false,
639        }
640    }
641
642    /// NOT LIKE pattern match
643    pub fn not_like(self, pattern: impl Into<String>) -> Self {
644        Expr::Like {
645            expr: Box::new(self),
646            pattern: pattern.into(),
647            negated: true,
648            case_insensitive: false,
649        }
650    }
651
652    /// ILIKE (case-insensitive) pattern match (PostgreSQL)
653    pub fn ilike(self, pattern: impl Into<String>) -> Self {
654        Expr::Like {
655            expr: Box::new(self),
656            pattern: pattern.into(),
657            negated: false,
658            case_insensitive: true,
659        }
660    }
661
662    /// NOT ILIKE pattern match (PostgreSQL)
663    pub fn not_ilike(self, pattern: impl Into<String>) -> Self {
664        Expr::Like {
665            expr: Box::new(self),
666            pattern: pattern.into(),
667            negated: true,
668            case_insensitive: true,
669        }
670    }
671
672    /// Check if column contains the given substring (LIKE '%pattern%').
673    ///
674    /// # Example
675    /// ```ignore
676    /// Expr::col("name").contains("man")
677    /// // Generates: "name" LIKE '%man%'
678    /// ```
679    pub fn contains(self, pattern: impl AsRef<str>) -> Self {
680        let pattern = format!("%{}%", pattern.as_ref());
681        Expr::Like {
682            expr: Box::new(self),
683            pattern,
684            negated: false,
685            case_insensitive: false,
686        }
687    }
688
689    /// Check if column starts with the given prefix (LIKE 'pattern%').
690    ///
691    /// # Example
692    /// ```ignore
693    /// Expr::col("name").starts_with("Spider")
694    /// // Generates: "name" LIKE 'Spider%'
695    /// ```
696    pub fn starts_with(self, pattern: impl AsRef<str>) -> Self {
697        let pattern = format!("{}%", pattern.as_ref());
698        Expr::Like {
699            expr: Box::new(self),
700            pattern,
701            negated: false,
702            case_insensitive: false,
703        }
704    }
705
706    /// Check if column ends with the given suffix (LIKE '%pattern').
707    ///
708    /// # Example
709    /// ```ignore
710    /// Expr::col("name").ends_with("man")
711    /// // Generates: "name" LIKE '%man'
712    /// ```
713    pub fn ends_with(self, pattern: impl AsRef<str>) -> Self {
714        let pattern = format!("%{}", pattern.as_ref());
715        Expr::Like {
716            expr: Box::new(self),
717            pattern,
718            negated: false,
719            case_insensitive: false,
720        }
721    }
722
723    /// Case-insensitive contains (ILIKE '%pattern%' or LOWER fallback).
724    pub fn icontains(self, pattern: impl AsRef<str>) -> Self {
725        let pattern = format!("%{}%", pattern.as_ref());
726        Expr::Like {
727            expr: Box::new(self),
728            pattern,
729            negated: false,
730            case_insensitive: true,
731        }
732    }
733
734    /// Case-insensitive starts_with (ILIKE 'pattern%' or LOWER fallback).
735    pub fn istarts_with(self, pattern: impl AsRef<str>) -> Self {
736        let pattern = format!("{}%", pattern.as_ref());
737        Expr::Like {
738            expr: Box::new(self),
739            pattern,
740            negated: false,
741            case_insensitive: true,
742        }
743    }
744
745    /// Case-insensitive ends_with (ILIKE '%pattern' or LOWER fallback).
746    pub fn iends_with(self, pattern: impl AsRef<str>) -> Self {
747        let pattern = format!("%{}", pattern.as_ref());
748        Expr::Like {
749            expr: Box::new(self),
750            pattern,
751            negated: false,
752            case_insensitive: true,
753        }
754    }
755
756    // ==================== IN Expressions ====================
757
758    /// IN list of values
759    pub fn in_list(self, values: Vec<impl Into<Expr>>) -> Self {
760        if values.is_empty() {
761            return Expr::raw("1 = 0");
762        }
763        Expr::In {
764            expr: Box::new(self),
765            values: values.into_iter().map(Into::into).collect(),
766            negated: false,
767        }
768    }
769
770    /// NOT IN list of values
771    pub fn not_in_list(self, values: Vec<impl Into<Expr>>) -> Self {
772        if values.is_empty() {
773            return Expr::raw("1 = 1");
774        }
775        Expr::In {
776            expr: Box::new(self),
777            values: values.into_iter().map(Into::into).collect(),
778            negated: true,
779        }
780    }
781
782    // ==================== BETWEEN ====================
783
784    /// BETWEEN low AND high
785    pub fn between(self, low: impl Into<Expr>, high: impl Into<Expr>) -> Self {
786        Expr::Between {
787            expr: Box::new(self),
788            low: Box::new(low.into()),
789            high: Box::new(high.into()),
790            negated: false,
791        }
792    }
793
794    /// NOT BETWEEN low AND high
795    pub fn not_between(self, low: impl Into<Expr>, high: impl Into<Expr>) -> Self {
796        Expr::Between {
797            expr: Box::new(self),
798            low: Box::new(low.into()),
799            high: Box::new(high.into()),
800            negated: true,
801        }
802    }
803
804    // ==================== Arithmetic Operators ====================
805
806    /// Addition (+)
807    pub fn add(self, other: impl Into<Expr>) -> Self {
808        Expr::Binary {
809            left: Box::new(self),
810            op: BinaryOp::Add,
811            right: Box::new(other.into()),
812        }
813    }
814
815    /// Subtraction (-)
816    pub fn sub(self, other: impl Into<Expr>) -> Self {
817        Expr::Binary {
818            left: Box::new(self),
819            op: BinaryOp::Sub,
820            right: Box::new(other.into()),
821        }
822    }
823
824    /// Multiplication (*)
825    pub fn mul(self, other: impl Into<Expr>) -> Self {
826        Expr::Binary {
827            left: Box::new(self),
828            op: BinaryOp::Mul,
829            right: Box::new(other.into()),
830        }
831    }
832
833    /// Division (/)
834    pub fn div(self, other: impl Into<Expr>) -> Self {
835        Expr::Binary {
836            left: Box::new(self),
837            op: BinaryOp::Div,
838            right: Box::new(other.into()),
839        }
840    }
841
842    /// Modulo (%)
843    pub fn modulo(self, other: impl Into<Expr>) -> Self {
844        Expr::Binary {
845            left: Box::new(self),
846            op: BinaryOp::Mod,
847            right: Box::new(other.into()),
848        }
849    }
850
851    /// Negation (unary -)
852    pub fn neg(self) -> Self {
853        Expr::Unary {
854            op: UnaryOp::Neg,
855            expr: Box::new(self),
856        }
857    }
858
859    // ==================== String Operations ====================
860
861    /// String concatenation (||)
862    pub fn concat(self, other: impl Into<Expr>) -> Self {
863        Expr::Binary {
864            left: Box::new(self),
865            op: BinaryOp::Concat,
866            right: Box::new(other.into()),
867        }
868    }
869
870    // ==================== Array Operations (PostgreSQL) ====================
871
872    /// Array contains (@>). Tests if this array contains all elements of `other`.
873    pub fn array_contains(self, other: impl Into<Expr>) -> Self {
874        Expr::Binary {
875            left: Box::new(self),
876            op: BinaryOp::ArrayContains,
877            right: Box::new(other.into()),
878        }
879    }
880
881    /// Array contained by (<@). Tests if this array is contained by `other`.
882    pub fn array_contained_by(self, other: impl Into<Expr>) -> Self {
883        Expr::Binary {
884            left: Box::new(self),
885            op: BinaryOp::ArrayContainedBy,
886            right: Box::new(other.into()),
887        }
888    }
889
890    /// Array overlap (&&). Tests if this array has any elements in common with `other`.
891    pub fn array_overlap(self, other: impl Into<Expr>) -> Self {
892        Expr::Binary {
893            left: Box::new(self),
894            op: BinaryOp::ArrayOverlap,
895            right: Box::new(other.into()),
896        }
897    }
898
899    /// ANY(array) = value. Tests if any element of the array equals the value.
900    ///
901    /// Generates: `value = ANY(array_column)`
902    pub fn array_any_eq(self, value: impl Into<Expr>) -> Self {
903        let val = value.into();
904        Expr::Binary {
905            left: Box::new(val),
906            op: BinaryOp::Eq,
907            right: Box::new(Expr::Function {
908                name: "ANY".to_string(),
909                args: vec![self],
910            }),
911        }
912    }
913
914    // ==================== Bitwise Operators ====================
915
916    /// Bitwise AND (&)
917    pub fn bit_and(self, other: impl Into<Expr>) -> Self {
918        Expr::Binary {
919            left: Box::new(self),
920            op: BinaryOp::BitAnd,
921            right: Box::new(other.into()),
922        }
923    }
924
925    /// Bitwise OR (|)
926    pub fn bit_or(self, other: impl Into<Expr>) -> Self {
927        Expr::Binary {
928            left: Box::new(self),
929            op: BinaryOp::BitOr,
930            right: Box::new(other.into()),
931        }
932    }
933
934    /// Bitwise XOR (^)
935    pub fn bit_xor(self, other: impl Into<Expr>) -> Self {
936        Expr::Binary {
937            left: Box::new(self),
938            op: BinaryOp::BitXor,
939            right: Box::new(other.into()),
940        }
941    }
942
943    /// Bitwise NOT (~)
944    pub fn bit_not(self) -> Self {
945        Expr::Unary {
946            op: UnaryOp::BitwiseNot,
947            expr: Box::new(self),
948        }
949    }
950
951    // ==================== CASE Expression ====================
952
953    /// Start building a CASE expression.
954    ///
955    /// # Example
956    /// ```ignore
957    /// Expr::case()
958    ///     .when(Expr::col("status").eq("active"), "Yes")
959    ///     .when(Expr::col("status").eq("pending"), "Maybe")
960    ///     .otherwise("No")
961    /// ```
962    pub fn case() -> CaseBuilder {
963        CaseBuilder {
964            when_clauses: Vec::new(),
965        }
966    }
967
968    // ==================== Aggregate Functions ====================
969
970    /// COUNT(*) aggregate function.
971    pub fn count_star() -> Self {
972        Expr::CountStar
973    }
974
975    /// COUNT(expr) aggregate function.
976    pub fn count(self) -> Self {
977        Expr::Function {
978            name: "COUNT".to_string(),
979            args: vec![self],
980        }
981    }
982
983    /// SUM(expr) aggregate function.
984    pub fn sum(self) -> Self {
985        Expr::Function {
986            name: "SUM".to_string(),
987            args: vec![self],
988        }
989    }
990
991    /// AVG(expr) aggregate function.
992    pub fn avg(self) -> Self {
993        Expr::Function {
994            name: "AVG".to_string(),
995            args: vec![self],
996        }
997    }
998
999    /// MIN(expr) aggregate function.
1000    pub fn min(self) -> Self {
1001        Expr::Function {
1002            name: "MIN".to_string(),
1003            args: vec![self],
1004        }
1005    }
1006
1007    /// MAX(expr) aggregate function.
1008    pub fn max(self) -> Self {
1009        Expr::Function {
1010            name: "MAX".to_string(),
1011            args: vec![self],
1012        }
1013    }
1014
1015    /// Create a generic function call.
1016    pub fn function(name: impl Into<String>, args: Vec<Expr>) -> Self {
1017        Expr::Function {
1018            name: name.into(),
1019            args,
1020        }
1021    }
1022
1023    // ==================== Window Functions ====================
1024
1025    /// ROW_NUMBER() window function.
1026    /// Returns the sequential number of a row within a partition.
1027    pub fn row_number() -> Self {
1028        Expr::Function {
1029            name: "ROW_NUMBER".to_string(),
1030            args: vec![],
1031        }
1032    }
1033
1034    /// RANK() window function.
1035    /// Returns the rank of the current row with gaps.
1036    pub fn rank() -> Self {
1037        Expr::Function {
1038            name: "RANK".to_string(),
1039            args: vec![],
1040        }
1041    }
1042
1043    /// DENSE_RANK() window function.
1044    /// Returns the rank of the current row without gaps.
1045    pub fn dense_rank() -> Self {
1046        Expr::Function {
1047            name: "DENSE_RANK".to_string(),
1048            args: vec![],
1049        }
1050    }
1051
1052    /// PERCENT_RANK() window function.
1053    /// Returns the relative rank of the current row.
1054    pub fn percent_rank() -> Self {
1055        Expr::Function {
1056            name: "PERCENT_RANK".to_string(),
1057            args: vec![],
1058        }
1059    }
1060
1061    /// CUME_DIST() window function.
1062    /// Returns the cumulative distribution of a value.
1063    pub fn cume_dist() -> Self {
1064        Expr::Function {
1065            name: "CUME_DIST".to_string(),
1066            args: vec![],
1067        }
1068    }
1069
1070    /// NTILE(n) window function.
1071    /// Divides rows into n groups and returns the group number.
1072    pub fn ntile(n: i64) -> Self {
1073        Expr::Function {
1074            name: "NTILE".to_string(),
1075            args: vec![Expr::Literal(Value::BigInt(n))],
1076        }
1077    }
1078
1079    /// LAG(expr) window function with default offset of 1.
1080    /// Returns the value of expr from the row that precedes the current row.
1081    pub fn lag(self) -> Self {
1082        Expr::Function {
1083            name: "LAG".to_string(),
1084            args: vec![self],
1085        }
1086    }
1087
1088    /// LAG(expr, offset) window function.
1089    /// Returns the value of expr from the row at the given offset before current row.
1090    pub fn lag_offset(self, offset: i64) -> Self {
1091        Expr::Function {
1092            name: "LAG".to_string(),
1093            args: vec![self, Expr::Literal(Value::BigInt(offset))],
1094        }
1095    }
1096
1097    /// LAG(expr, offset, default) window function.
1098    /// Returns the value of expr or default if the offset row doesn't exist.
1099    pub fn lag_with_default(self, offset: i64, default: impl Into<Expr>) -> Self {
1100        Expr::Function {
1101            name: "LAG".to_string(),
1102            args: vec![self, Expr::Literal(Value::BigInt(offset)), default.into()],
1103        }
1104    }
1105
1106    /// LEAD(expr) window function with default offset of 1.
1107    /// Returns the value of expr from the row that follows the current row.
1108    pub fn lead(self) -> Self {
1109        Expr::Function {
1110            name: "LEAD".to_string(),
1111            args: vec![self],
1112        }
1113    }
1114
1115    /// LEAD(expr, offset) window function.
1116    /// Returns the value of expr from the row at the given offset after current row.
1117    pub fn lead_offset(self, offset: i64) -> Self {
1118        Expr::Function {
1119            name: "LEAD".to_string(),
1120            args: vec![self, Expr::Literal(Value::BigInt(offset))],
1121        }
1122    }
1123
1124    /// LEAD(expr, offset, default) window function.
1125    /// Returns the value of expr or default if the offset row doesn't exist.
1126    pub fn lead_with_default(self, offset: i64, default: impl Into<Expr>) -> Self {
1127        Expr::Function {
1128            name: "LEAD".to_string(),
1129            args: vec![self, Expr::Literal(Value::BigInt(offset)), default.into()],
1130        }
1131    }
1132
1133    /// FIRST_VALUE(expr) window function.
1134    /// Returns the first value within the window frame.
1135    pub fn first_value(self) -> Self {
1136        Expr::Function {
1137            name: "FIRST_VALUE".to_string(),
1138            args: vec![self],
1139        }
1140    }
1141
1142    /// LAST_VALUE(expr) window function.
1143    /// Returns the last value within the window frame.
1144    pub fn last_value(self) -> Self {
1145        Expr::Function {
1146            name: "LAST_VALUE".to_string(),
1147            args: vec![self],
1148        }
1149    }
1150
1151    /// NTH_VALUE(expr, n) window function.
1152    /// Returns the nth value within the window frame.
1153    pub fn nth_value(self, n: i64) -> Self {
1154        Expr::Function {
1155            name: "NTH_VALUE".to_string(),
1156            args: vec![self, Expr::Literal(Value::BigInt(n))],
1157        }
1158    }
1159
1160    // ==================== Window OVER Clause ====================
1161
1162    /// Start building a window function with OVER clause.
1163    ///
1164    /// # Example
1165    /// ```ignore
1166    /// // ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC)
1167    /// Expr::row_number()
1168    ///     .over()
1169    ///     .partition_by(Expr::col("department"))
1170    ///     .order_by(Expr::col("salary").desc())
1171    ///     .build()
1172    ///
1173    /// // SUM(amount) OVER (PARTITION BY customer_id)
1174    /// Expr::col("amount").sum()
1175    ///     .over()
1176    ///     .partition_by(Expr::col("customer_id"))
1177    ///     .build()
1178    /// ```
1179    pub fn over(self) -> WindowBuilder {
1180        WindowBuilder {
1181            function: self,
1182            partition_by: Vec::new(),
1183            order_by: Vec::new(),
1184            frame: None,
1185        }
1186    }
1187
1188    // ==================== NULL Handling Functions ====================
1189
1190    /// COALESCE function: returns the first non-NULL argument.
1191    ///
1192    /// # Example
1193    /// ```ignore
1194    /// Expr::coalesce(vec![Expr::col("nickname"), Expr::col("name"), Expr::lit("Anonymous")])
1195    /// // Generates: COALESCE("nickname", "name", 'Anonymous')
1196    /// ```
1197    pub fn coalesce(args: Vec<impl Into<Expr>>) -> Self {
1198        Expr::Function {
1199            name: "COALESCE".to_string(),
1200            args: args.into_iter().map(Into::into).collect(),
1201        }
1202    }
1203
1204    /// NULLIF function: returns NULL if both arguments are equal, otherwise returns the first.
1205    ///
1206    /// # Example
1207    /// ```ignore
1208    /// Expr::nullif(Expr::col("value"), Expr::lit(0))
1209    /// // Generates: NULLIF("value", 0)
1210    /// ```
1211    pub fn nullif(expr1: impl Into<Expr>, expr2: impl Into<Expr>) -> Self {
1212        Expr::Function {
1213            name: "NULLIF".to_string(),
1214            args: vec![expr1.into(), expr2.into()],
1215        }
1216    }
1217
1218    /// IFNULL/NVL function (dialect-specific): returns expr2 if expr1 is NULL.
1219    ///
1220    /// This generates IFNULL for SQLite/MySQL or COALESCE for PostgreSQL.
1221    pub fn ifnull(expr1: impl Into<Expr>, expr2: impl Into<Expr>) -> Self {
1222        // Use COALESCE as it's more portable
1223        Expr::Function {
1224            name: "COALESCE".to_string(),
1225            args: vec![expr1.into(), expr2.into()],
1226        }
1227    }
1228
1229    // ==================== String Functions ====================
1230
1231    /// UPPER function: converts string to uppercase.
1232    pub fn upper(self) -> Self {
1233        Expr::Function {
1234            name: "UPPER".to_string(),
1235            args: vec![self],
1236        }
1237    }
1238
1239    /// LOWER function: converts string to lowercase.
1240    pub fn lower(self) -> Self {
1241        Expr::Function {
1242            name: "LOWER".to_string(),
1243            args: vec![self],
1244        }
1245    }
1246
1247    /// LENGTH function: returns the length of a string.
1248    pub fn length(self) -> Self {
1249        Expr::Function {
1250            name: "LENGTH".to_string(),
1251            args: vec![self],
1252        }
1253    }
1254
1255    /// TRIM function: removes leading and trailing whitespace.
1256    pub fn trim(self) -> Self {
1257        Expr::Function {
1258            name: "TRIM".to_string(),
1259            args: vec![self],
1260        }
1261    }
1262
1263    /// LTRIM function: removes leading whitespace.
1264    pub fn ltrim(self) -> Self {
1265        Expr::Function {
1266            name: "LTRIM".to_string(),
1267            args: vec![self],
1268        }
1269    }
1270
1271    /// RTRIM function: removes trailing whitespace.
1272    pub fn rtrim(self) -> Self {
1273        Expr::Function {
1274            name: "RTRIM".to_string(),
1275            args: vec![self],
1276        }
1277    }
1278
1279    /// SUBSTR/SUBSTRING function: extracts a substring.
1280    ///
1281    /// # Arguments
1282    /// * `start` - 1-based start position
1283    /// * `length` - Optional length of substring
1284    pub fn substr(self, start: impl Into<Expr>, length: Option<impl Into<Expr>>) -> Self {
1285        let mut args = vec![self, start.into()];
1286        if let Some(len) = length {
1287            args.push(len.into());
1288        }
1289        Expr::Function {
1290            name: "SUBSTR".to_string(),
1291            args,
1292        }
1293    }
1294
1295    /// REPLACE function: replaces occurrences of a substring.
1296    pub fn replace(self, from: impl Into<Expr>, to: impl Into<Expr>) -> Self {
1297        Expr::Function {
1298            name: "REPLACE".to_string(),
1299            args: vec![self, from.into(), to.into()],
1300        }
1301    }
1302
1303    // ==================== Numeric Functions ====================
1304
1305    /// ABS function: returns absolute value.
1306    pub fn abs(self) -> Self {
1307        Expr::Function {
1308            name: "ABS".to_string(),
1309            args: vec![self],
1310        }
1311    }
1312
1313    /// ROUND function: rounds to specified decimal places.
1314    pub fn round(self, decimals: impl Into<Expr>) -> Self {
1315        Expr::Function {
1316            name: "ROUND".to_string(),
1317            args: vec![self, decimals.into()],
1318        }
1319    }
1320
1321    /// FLOOR function: rounds down to nearest integer.
1322    pub fn floor(self) -> Self {
1323        Expr::Function {
1324            name: "FLOOR".to_string(),
1325            args: vec![self],
1326        }
1327    }
1328
1329    /// CEIL/CEILING function: rounds up to nearest integer.
1330    pub fn ceil(self) -> Self {
1331        Expr::Function {
1332            name: "CEIL".to_string(),
1333            args: vec![self],
1334        }
1335    }
1336
1337    // ==================== Ordering ====================
1338
1339    /// Create an ascending ORDER BY expression.
1340    pub fn asc(self) -> OrderBy {
1341        OrderBy {
1342            expr: self,
1343            direction: OrderDirection::Asc,
1344            nulls: None,
1345        }
1346    }
1347
1348    /// Create a descending ORDER BY expression.
1349    pub fn desc(self) -> OrderBy {
1350        OrderBy {
1351            expr: self,
1352            direction: OrderDirection::Desc,
1353            nulls: None,
1354        }
1355    }
1356
1357    // ==================== Utility ====================
1358
1359    /// Wrap expression in parentheses.
1360    pub fn paren(self) -> Self {
1361        Expr::Paren(Box::new(self))
1362    }
1363
1364    /// Create a subquery expression.
1365    pub fn subquery(sql: impl Into<String>) -> Self {
1366        Expr::Subquery(sql.into())
1367    }
1368
1369    // ==================== EXISTS Expressions ====================
1370
1371    /// Create an EXISTS subquery expression.
1372    ///
1373    /// # Arguments
1374    /// * `subquery_sql` - The SELECT subquery SQL (without outer parentheses)
1375    /// * `params` - Parameters for the subquery
1376    ///
1377    /// # Example
1378    /// ```ignore
1379    /// // EXISTS (SELECT 1 FROM orders WHERE orders.customer_id = customers.id)
1380    /// Expr::exists(
1381    ///     "SELECT 1 FROM orders WHERE orders.customer_id = customers.id",
1382    ///     vec![]
1383    /// )
1384    /// ```
1385    pub fn exists(subquery_sql: impl Into<String>, params: Vec<Value>) -> Self {
1386        Expr::Exists {
1387            subquery: subquery_sql.into(),
1388            params,
1389            negated: false,
1390        }
1391    }
1392
1393    /// Create a NOT EXISTS subquery expression.
1394    ///
1395    /// # Arguments
1396    /// * `subquery_sql` - The SELECT subquery SQL (without outer parentheses)
1397    /// * `params` - Parameters for the subquery
1398    ///
1399    /// # Example
1400    /// ```ignore
1401    /// // NOT EXISTS (SELECT 1 FROM orders WHERE orders.customer_id = customers.id)
1402    /// Expr::not_exists(
1403    ///     "SELECT 1 FROM orders WHERE orders.customer_id = customers.id",
1404    ///     vec![]
1405    /// )
1406    /// ```
1407    pub fn not_exists(subquery_sql: impl Into<String>, params: Vec<Value>) -> Self {
1408        Expr::Exists {
1409            subquery: subquery_sql.into(),
1410            params,
1411            negated: true,
1412        }
1413    }
1414
1415    // ==================== JSON Functions ====================
1416
1417    /// Extract a JSON value by key (returns JSON).
1418    ///
1419    /// Generates dialect-specific SQL:
1420    /// - PostgreSQL: `expr -> 'key'`
1421    /// - MySQL: `JSON_EXTRACT(expr, '$.key')`
1422    /// - SQLite: `json_extract(expr, '$.key')`
1423    ///
1424    /// # Example
1425    /// ```ignore
1426    /// Expr::col("data").json_get("name")
1427    /// // PostgreSQL: "data" -> 'name'
1428    /// // MySQL: JSON_EXTRACT("data", '$.name')
1429    /// ```
1430    pub fn json_get(self, key: impl Into<String>) -> Self {
1431        Expr::JsonExtract {
1432            expr: Box::new(self),
1433            path: JsonPath::Key(key.into()),
1434        }
1435    }
1436
1437    /// Extract a JSON value by array index (returns JSON).
1438    ///
1439    /// Generates dialect-specific SQL:
1440    /// - PostgreSQL: `expr -> 0`
1441    /// - MySQL: `JSON_EXTRACT(expr, '$[0]')`
1442    /// - SQLite: `json_extract(expr, '$[0]')`
1443    ///
1444    /// # Example
1445    /// ```ignore
1446    /// Expr::col("items").json_get_index(0)
1447    /// // PostgreSQL: "items" -> 0
1448    /// ```
1449    pub fn json_get_index(self, index: i64) -> Self {
1450        Expr::JsonExtract {
1451            expr: Box::new(self),
1452            path: JsonPath::Index(index),
1453        }
1454    }
1455
1456    /// Extract a JSON value as text by key.
1457    ///
1458    /// Generates dialect-specific SQL:
1459    /// - PostgreSQL: `expr ->> 'key'`
1460    /// - MySQL: `JSON_UNQUOTE(JSON_EXTRACT(expr, '$.key'))`
1461    /// - SQLite: `json_extract(expr, '$.key')` (returns text)
1462    ///
1463    /// # Example
1464    /// ```ignore
1465    /// Expr::col("data").json_get_text("name")
1466    /// // PostgreSQL: "data" ->> 'name'
1467    /// ```
1468    pub fn json_get_text(self, key: impl Into<String>) -> Self {
1469        Expr::JsonExtractText {
1470            expr: Box::new(self),
1471            path: JsonPath::Key(key.into()),
1472        }
1473    }
1474
1475    /// Extract a JSON value as text by array index.
1476    ///
1477    /// # Example
1478    /// ```ignore
1479    /// Expr::col("items").json_get_text_index(0)
1480    /// // PostgreSQL: "items" ->> 0
1481    /// ```
1482    pub fn json_get_text_index(self, index: i64) -> Self {
1483        Expr::JsonExtractText {
1484            expr: Box::new(self),
1485            path: JsonPath::Index(index),
1486        }
1487    }
1488
1489    /// Extract a nested JSON value by path (returns JSON).
1490    ///
1491    /// Generates dialect-specific SQL:
1492    /// - PostgreSQL: `expr #> '{path, to, value}'`
1493    /// - MySQL: `JSON_EXTRACT(expr, '$.path.to.value')`
1494    /// - SQLite: `json_extract(expr, '$.path.to.value')`
1495    ///
1496    /// # Example
1497    /// ```ignore
1498    /// Expr::col("data").json_path(&["address", "city"])
1499    /// // PostgreSQL: "data" #> '{address, city}'
1500    /// ```
1501    pub fn json_path(self, path: &[&str]) -> Self {
1502        Expr::JsonExtractPath {
1503            expr: Box::new(self),
1504            path: path.iter().map(|s| (*s).to_string()).collect(),
1505        }
1506    }
1507
1508    /// Extract a nested JSON value by path as text.
1509    ///
1510    /// Generates dialect-specific SQL:
1511    /// - PostgreSQL: `expr #>> '{path, to, value}'`
1512    /// - MySQL: `JSON_UNQUOTE(JSON_EXTRACT(expr, '$.path.to.value'))`
1513    /// - SQLite: `json_extract(expr, '$.path.to.value')`
1514    ///
1515    /// # Example
1516    /// ```ignore
1517    /// Expr::col("data").json_path_text(&["address", "city"])
1518    /// // PostgreSQL: "data" #>> '{address, city}'
1519    /// ```
1520    pub fn json_path_text(self, path: &[&str]) -> Self {
1521        Expr::JsonExtractPathText {
1522            expr: Box::new(self),
1523            path: path.iter().map(|s| (*s).to_string()).collect(),
1524        }
1525    }
1526
1527    /// Check if JSON contains another JSON value.
1528    ///
1529    /// Generates dialect-specific SQL:
1530    /// - PostgreSQL: `expr @> other` (JSONB only)
1531    /// - MySQL: `JSON_CONTAINS(expr, other)`
1532    ///
1533    /// # Example
1534    /// ```ignore
1535    /// Expr::col("tags").json_contains(Expr::lit(r#"["rust"]"#))
1536    /// // PostgreSQL: "tags" @> '["rust"]'
1537    /// ```
1538    pub fn json_contains(self, other: impl Into<Expr>) -> Self {
1539        Expr::JsonContains {
1540            expr: Box::new(self),
1541            other: Box::new(other.into()),
1542        }
1543    }
1544
1545    /// Check if JSON is contained by another JSON value.
1546    ///
1547    /// Generates dialect-specific SQL:
1548    /// - PostgreSQL: `expr <@ other` (JSONB only)
1549    /// - MySQL: `JSON_CONTAINS(other, expr)`
1550    ///
1551    /// # Example
1552    /// ```ignore
1553    /// Expr::col("tags").json_contained_by(Expr::lit(r#"["rust", "python", "go"]"#))
1554    /// ```
1555    pub fn json_contained_by(self, other: impl Into<Expr>) -> Self {
1556        Expr::JsonContainedBy {
1557            expr: Box::new(self),
1558            other: Box::new(other.into()),
1559        }
1560    }
1561
1562    /// Check if JSON object has a specific key.
1563    ///
1564    /// Generates dialect-specific SQL:
1565    /// - PostgreSQL: `expr ? 'key'` (JSONB only)
1566    /// - MySQL: `JSON_CONTAINS_PATH(expr, 'one', '$.key')`
1567    /// - SQLite: `json_type(expr, '$.key') IS NOT NULL`
1568    ///
1569    /// # Example
1570    /// ```ignore
1571    /// Expr::col("data").json_has_key("email")
1572    /// // PostgreSQL: "data" ? 'email'
1573    /// ```
1574    pub fn json_has_key(self, key: impl Into<String>) -> Self {
1575        Expr::JsonHasKey {
1576            expr: Box::new(self),
1577            key: key.into(),
1578        }
1579    }
1580
1581    /// Check if JSON object has any of the specified keys.
1582    ///
1583    /// Generates dialect-specific SQL:
1584    /// - PostgreSQL: `expr ?| array['key1', 'key2']` (JSONB only)
1585    /// - MySQL: `JSON_CONTAINS_PATH(expr, 'one', '$.key1', '$.key2')`
1586    ///
1587    /// # Example
1588    /// ```ignore
1589    /// Expr::col("data").json_has_any_key(&["email", "phone"])
1590    /// // PostgreSQL: "data" ?| array['email', 'phone']
1591    /// ```
1592    pub fn json_has_any_key(self, keys: &[&str]) -> Self {
1593        Expr::JsonHasAnyKey {
1594            expr: Box::new(self),
1595            keys: keys.iter().map(|s| (*s).to_string()).collect(),
1596        }
1597    }
1598
1599    /// Check if JSON object has all of the specified keys.
1600    ///
1601    /// Generates dialect-specific SQL:
1602    /// - PostgreSQL: `expr ?& array['key1', 'key2']` (JSONB only)
1603    /// - MySQL: `JSON_CONTAINS_PATH(expr, 'all', '$.key1', '$.key2')`
1604    ///
1605    /// # Example
1606    /// ```ignore
1607    /// Expr::col("data").json_has_all_keys(&["email", "phone"])
1608    /// // PostgreSQL: "data" ?& array['email', 'phone']
1609    /// ```
1610    pub fn json_has_all_keys(self, keys: &[&str]) -> Self {
1611        Expr::JsonHasAllKeys {
1612            expr: Box::new(self),
1613            keys: keys.iter().map(|s| (*s).to_string()).collect(),
1614        }
1615    }
1616
1617    /// Get the length of a JSON array.
1618    ///
1619    /// Generates dialect-specific SQL:
1620    /// - PostgreSQL: `jsonb_array_length(expr)`
1621    /// - MySQL: `JSON_LENGTH(expr)`
1622    /// - SQLite: `json_array_length(expr)`
1623    ///
1624    /// # Example
1625    /// ```ignore
1626    /// Expr::col("items").json_array_length()
1627    /// ```
1628    pub fn json_array_length(self) -> Self {
1629        Expr::JsonArrayLength {
1630            expr: Box::new(self),
1631        }
1632    }
1633
1634    /// Get the type of a JSON value.
1635    ///
1636    /// Generates dialect-specific SQL:
1637    /// - PostgreSQL: `jsonb_typeof(expr)`
1638    /// - MySQL: `JSON_TYPE(expr)`
1639    /// - SQLite: `json_type(expr)`
1640    ///
1641    /// # Example
1642    /// ```ignore
1643    /// Expr::col("data").json_typeof()
1644    /// ```
1645    pub fn json_typeof(self) -> Self {
1646        Expr::JsonTypeof {
1647            expr: Box::new(self),
1648        }
1649    }
1650
1651    // ==================== SQL Generation ====================
1652
1653    /// Build SQL string and collect parameters (default PostgreSQL dialect).
1654    pub fn build(&self, params: &mut Vec<Value>, offset: usize) -> String {
1655        self.build_with_dialect(Dialect::Postgres, params, offset)
1656    }
1657
1658    /// Build SQL string with specific dialect.
1659    pub fn build_with_dialect(
1660        &self,
1661        dialect: Dialect,
1662        params: &mut Vec<Value>,
1663        offset: usize,
1664    ) -> String {
1665        match self {
1666            Expr::Column { table, name } => {
1667                if let Some(t) = table {
1668                    format!(
1669                        "{}.{}",
1670                        dialect.quote_identifier(t),
1671                        dialect.quote_identifier(name)
1672                    )
1673                } else {
1674                    dialect.quote_identifier(name)
1675                }
1676            }
1677
1678            Expr::Literal(value) => {
1679                if matches!(value, Value::Default) {
1680                    "DEFAULT".to_string()
1681                } else {
1682                    params.push(value.clone());
1683                    dialect.placeholder(offset + params.len())
1684                }
1685            }
1686
1687            Expr::Placeholder(idx) => dialect.placeholder(*idx),
1688
1689            Expr::Binary { left, op, right } => {
1690                let left_sql = left.build_with_dialect(dialect, params, offset);
1691                let right_sql = right.build_with_dialect(dialect, params, offset);
1692                if *op == BinaryOp::Concat && dialect == Dialect::Mysql {
1693                    format!("CONCAT({left_sql}, {right_sql})")
1694                } else {
1695                    format!("{left_sql} {} {right_sql}", op.as_str())
1696                }
1697            }
1698
1699            Expr::Unary { op, expr } => {
1700                let expr_sql = expr.build_with_dialect(dialect, params, offset);
1701                match op {
1702                    UnaryOp::Not => format!("NOT {expr_sql}"),
1703                    UnaryOp::Neg => format!("-{expr_sql}"),
1704                    UnaryOp::BitwiseNot => format!("~{expr_sql}"),
1705                }
1706            }
1707
1708            Expr::Function { name, args } => {
1709                let arg_sqls: Vec<_> = args
1710                    .iter()
1711                    .map(|a| a.build_with_dialect(dialect, params, offset))
1712                    .collect();
1713                format!("{name}({})", arg_sqls.join(", "))
1714            }
1715
1716            Expr::Case {
1717                when_clauses,
1718                else_clause,
1719            } => {
1720                let mut sql = String::from("CASE");
1721                for (condition, result) in when_clauses {
1722                    let cond_sql = condition.build_with_dialect(dialect, params, offset);
1723                    let result_sql = result.build_with_dialect(dialect, params, offset);
1724                    sql.push_str(&format!(" WHEN {cond_sql} THEN {result_sql}"));
1725                }
1726                if let Some(else_expr) = else_clause {
1727                    let else_sql = else_expr.build_with_dialect(dialect, params, offset);
1728                    sql.push_str(&format!(" ELSE {else_sql}"));
1729                }
1730                sql.push_str(" END");
1731                sql
1732            }
1733
1734            Expr::In {
1735                expr,
1736                values,
1737                negated,
1738            } => {
1739                let expr_sql = expr.build_with_dialect(dialect, params, offset);
1740                let value_sqls: Vec<_> = values
1741                    .iter()
1742                    .map(|v| v.build_with_dialect(dialect, params, offset))
1743                    .collect();
1744                let not_str = if *negated { "NOT " } else { "" };
1745                format!("{expr_sql} {not_str}IN ({})", value_sqls.join(", "))
1746            }
1747
1748            Expr::Between {
1749                expr,
1750                low,
1751                high,
1752                negated,
1753            } => {
1754                let expr_sql = expr.build_with_dialect(dialect, params, offset);
1755                let low_sql = low.build_with_dialect(dialect, params, offset);
1756                let high_sql = high.build_with_dialect(dialect, params, offset);
1757                let not_str = if *negated { "NOT " } else { "" };
1758                format!("{expr_sql} {not_str}BETWEEN {low_sql} AND {high_sql}")
1759            }
1760
1761            Expr::IsNull { expr, negated } => {
1762                let expr_sql = expr.build_with_dialect(dialect, params, offset);
1763                let not_str = if *negated { " NOT" } else { "" };
1764                format!("{expr_sql} IS{not_str} NULL")
1765            }
1766
1767            Expr::IsDistinctFrom {
1768                left,
1769                right,
1770                negated,
1771            } => {
1772                let left_sql = left.build_with_dialect(dialect, params, offset);
1773                let right_sql = right.build_with_dialect(dialect, params, offset);
1774                let not_str = if *negated { " NOT" } else { "" };
1775                // Standard SQL syntax supported by PostgreSQL, SQLite 3.39+, MySQL 8.0.16+
1776                format!("{left_sql} IS{not_str} DISTINCT FROM {right_sql}")
1777            }
1778
1779            Expr::Cast { expr, type_name } => {
1780                let expr_sql = expr.build_with_dialect(dialect, params, offset);
1781                format!("CAST({expr_sql} AS {type_name})")
1782            }
1783
1784            Expr::Like {
1785                expr,
1786                pattern,
1787                negated,
1788                case_insensitive,
1789            } => {
1790                let expr_sql = expr.build_with_dialect(dialect, params, offset);
1791                params.push(Value::Text(pattern.clone()));
1792                let param = dialect.placeholder(offset + params.len());
1793                let not_str = if *negated { "NOT " } else { "" };
1794                let op = if *case_insensitive && dialect.supports_ilike() {
1795                    "ILIKE"
1796                } else if *case_insensitive {
1797                    // Fallback for dialects without ILIKE
1798                    return format!("LOWER({expr_sql}) {not_str}LIKE LOWER({param})");
1799                } else {
1800                    "LIKE"
1801                };
1802                format!("{expr_sql} {not_str}{op} {param}")
1803            }
1804
1805            Expr::Subquery(sql) => format!("({sql})"),
1806
1807            Expr::Exists {
1808                subquery,
1809                params: subquery_params,
1810                negated,
1811            } => {
1812                // Add subquery parameters to the main params list
1813                // Note: The subquery SQL should use placeholders starting from 1,
1814                // and we'll adjust them based on the current offset
1815                let start_idx = offset + params.len();
1816                params.extend(subquery_params.iter().cloned());
1817
1818                // Simple approach: if there are params, we need to adjust placeholder indices
1819                // in the subquery SQL. For now, we assume the subquery uses PostgreSQL-style $N
1820                // placeholders and adjust them.
1821                let adjusted_subquery = if subquery_params.is_empty() {
1822                    subquery.clone()
1823                } else {
1824                    // Rewrite $1, $2, etc. to $start_idx+1, $start_idx+2, etc.
1825                    adjust_placeholder_indices(subquery, start_idx, dialect)
1826                };
1827
1828                let not_str = if *negated { "NOT " } else { "" };
1829                format!("{not_str}EXISTS ({adjusted_subquery})")
1830            }
1831
1832            Expr::Raw(sql) => sql.clone(),
1833
1834            Expr::Paren(expr) => {
1835                let expr_sql = expr.build_with_dialect(dialect, params, offset);
1836                format!("({expr_sql})")
1837            }
1838
1839            Expr::CountStar => "COUNT(*)".to_string(),
1840
1841            Expr::Window {
1842                function,
1843                partition_by,
1844                order_by,
1845                frame,
1846            } => {
1847                let func_sql = function.build_with_dialect(dialect, params, offset);
1848                let mut over_parts: Vec<String> = Vec::new();
1849
1850                // PARTITION BY clause
1851                if !partition_by.is_empty() {
1852                    let partition_sqls: Vec<_> = partition_by
1853                        .iter()
1854                        .map(|e| e.build_with_dialect(dialect, params, offset))
1855                        .collect();
1856                    over_parts.push(format!("PARTITION BY {}", partition_sqls.join(", ")));
1857                }
1858
1859                // ORDER BY clause
1860                if !order_by.is_empty() {
1861                    let order_sqls: Vec<_> = order_by
1862                        .iter()
1863                        .map(|o| {
1864                            let expr_sql = o.expr.build_with_dialect(dialect, params, offset);
1865                            let dir = match o.direction {
1866                                OrderDirection::Asc => "ASC",
1867                                OrderDirection::Desc => "DESC",
1868                            };
1869                            let nulls = match o.nulls {
1870                                Some(crate::clause::NullsOrder::First) => " NULLS FIRST",
1871                                Some(crate::clause::NullsOrder::Last) => " NULLS LAST",
1872                                None => "",
1873                            };
1874                            format!("{expr_sql} {dir}{nulls}")
1875                        })
1876                        .collect();
1877                    over_parts.push(format!("ORDER BY {}", order_sqls.join(", ")));
1878                }
1879
1880                // Frame specification
1881                if let Some(f) = frame {
1882                    let frame_sql = if let Some(end) = &f.end {
1883                        format!(
1884                            "{} BETWEEN {} AND {}",
1885                            f.frame_type.as_str(),
1886                            f.start.to_sql(),
1887                            end.to_sql()
1888                        )
1889                    } else {
1890                        format!("{} {}", f.frame_type.as_str(), f.start.to_sql())
1891                    };
1892                    over_parts.push(frame_sql);
1893                }
1894
1895                if over_parts.is_empty() {
1896                    format!("{func_sql} OVER ()")
1897                } else {
1898                    format!("{func_sql} OVER ({})", over_parts.join(" "))
1899                }
1900            }
1901
1902            // ==================== JSON Expressions ====================
1903            Expr::JsonExtract { expr, path } => {
1904                let expr_sql = expr.build_with_dialect(dialect, params, offset);
1905                match dialect {
1906                    Dialect::Postgres => match path {
1907                        JsonPath::Key(key) => format!("{expr_sql} -> '{key}'"),
1908                        JsonPath::Index(idx) => format!("{expr_sql} -> {idx}"),
1909                    },
1910                    Dialect::Mysql => {
1911                        let json_path = match path {
1912                            JsonPath::Key(key) => format!("$.{key}"),
1913                            JsonPath::Index(idx) => format!("$[{idx}]"),
1914                        };
1915                        format!("JSON_EXTRACT({expr_sql}, '{json_path}')")
1916                    }
1917                    Dialect::Sqlite => {
1918                        let json_path = match path {
1919                            JsonPath::Key(key) => format!("$.{key}"),
1920                            JsonPath::Index(idx) => format!("$[{idx}]"),
1921                        };
1922                        format!("json_extract({expr_sql}, '{json_path}')")
1923                    }
1924                }
1925            }
1926
1927            Expr::JsonExtractText { expr, path } => {
1928                let expr_sql = expr.build_with_dialect(dialect, params, offset);
1929                match dialect {
1930                    Dialect::Postgres => match path {
1931                        JsonPath::Key(key) => format!("{expr_sql} ->> '{key}'"),
1932                        JsonPath::Index(idx) => format!("{expr_sql} ->> {idx}"),
1933                    },
1934                    Dialect::Mysql => {
1935                        let json_path = match path {
1936                            JsonPath::Key(key) => format!("$.{key}"),
1937                            JsonPath::Index(idx) => format!("$[{idx}]"),
1938                        };
1939                        format!("JSON_UNQUOTE(JSON_EXTRACT({expr_sql}, '{json_path}'))")
1940                    }
1941                    Dialect::Sqlite => {
1942                        // SQLite's json_extract returns text for scalar values
1943                        let json_path = match path {
1944                            JsonPath::Key(key) => format!("$.{key}"),
1945                            JsonPath::Index(idx) => format!("$[{idx}]"),
1946                        };
1947                        format!("json_extract({expr_sql}, '{json_path}')")
1948                    }
1949                }
1950            }
1951
1952            Expr::JsonExtractPath { expr, path } => {
1953                let expr_sql = expr.build_with_dialect(dialect, params, offset);
1954                match dialect {
1955                    Dialect::Postgres => {
1956                        let path_array = path.join(", ");
1957                        format!("{expr_sql} #> '{{{path_array}}}'")
1958                    }
1959                    Dialect::Mysql | Dialect::Sqlite => {
1960                        let json_path = format!("$.{}", path.join("."));
1961                        let func = if dialect == Dialect::Mysql {
1962                            "JSON_EXTRACT"
1963                        } else {
1964                            "json_extract"
1965                        };
1966                        format!("{func}({expr_sql}, '{json_path}')")
1967                    }
1968                }
1969            }
1970
1971            Expr::JsonExtractPathText { expr, path } => {
1972                let expr_sql = expr.build_with_dialect(dialect, params, offset);
1973                match dialect {
1974                    Dialect::Postgres => {
1975                        let path_array = path.join(", ");
1976                        format!("{expr_sql} #>> '{{{path_array}}}'")
1977                    }
1978                    Dialect::Mysql => {
1979                        let json_path = format!("$.{}", path.join("."));
1980                        format!("JSON_UNQUOTE(JSON_EXTRACT({expr_sql}, '{json_path}'))")
1981                    }
1982                    Dialect::Sqlite => {
1983                        let json_path = format!("$.{}", path.join("."));
1984                        format!("json_extract({expr_sql}, '{json_path}')")
1985                    }
1986                }
1987            }
1988
1989            Expr::JsonContains { expr, other } => {
1990                let expr_sql = expr.build_with_dialect(dialect, params, offset);
1991                let other_sql = other.build_with_dialect(dialect, params, offset);
1992                match dialect {
1993                    Dialect::Postgres => format!("{expr_sql} @> {other_sql}"),
1994                    Dialect::Mysql => format!("JSON_CONTAINS({expr_sql}, {other_sql})"),
1995                    Dialect::Sqlite => {
1996                        // SQLite doesn't have direct containment, fallback to expression
1997                        format!(
1998                            "/* JSON containment not supported in SQLite */ ({expr_sql} = {other_sql})"
1999                        )
2000                    }
2001                }
2002            }
2003
2004            Expr::JsonContainedBy { expr, other } => {
2005                let expr_sql = expr.build_with_dialect(dialect, params, offset);
2006                let other_sql = other.build_with_dialect(dialect, params, offset);
2007                match dialect {
2008                    Dialect::Postgres => format!("{expr_sql} <@ {other_sql}"),
2009                    Dialect::Mysql => format!("JSON_CONTAINS({other_sql}, {expr_sql})"),
2010                    Dialect::Sqlite => {
2011                        format!(
2012                            "/* JSON contained-by not supported in SQLite */ ({expr_sql} = {other_sql})"
2013                        )
2014                    }
2015                }
2016            }
2017
2018            Expr::JsonHasKey { expr, key } => {
2019                let expr_sql = expr.build_with_dialect(dialect, params, offset);
2020                match dialect {
2021                    Dialect::Postgres => format!("{expr_sql} ? '{key}'"),
2022                    Dialect::Mysql => format!("JSON_CONTAINS_PATH({expr_sql}, 'one', '$.{key}')"),
2023                    Dialect::Sqlite => format!("json_type({expr_sql}, '$.{key}') IS NOT NULL"),
2024                }
2025            }
2026
2027            Expr::JsonHasAnyKey { expr, keys } => {
2028                let expr_sql = expr.build_with_dialect(dialect, params, offset);
2029                match dialect {
2030                    Dialect::Postgres => {
2031                        let keys_array = keys
2032                            .iter()
2033                            .map(|k| format!("'{k}'"))
2034                            .collect::<Vec<_>>()
2035                            .join(", ");
2036                        format!("{expr_sql} ?| array[{keys_array}]")
2037                    }
2038                    Dialect::Mysql => {
2039                        let paths = keys
2040                            .iter()
2041                            .map(|k| format!("'$.{k}'"))
2042                            .collect::<Vec<_>>()
2043                            .join(", ");
2044                        format!("JSON_CONTAINS_PATH({expr_sql}, 'one', {paths})")
2045                    }
2046                    Dialect::Sqlite => {
2047                        let checks = keys
2048                            .iter()
2049                            .map(|k| format!("json_type({expr_sql}, '$.{k}') IS NOT NULL"))
2050                            .collect::<Vec<_>>()
2051                            .join(" OR ");
2052                        format!("({checks})")
2053                    }
2054                }
2055            }
2056
2057            Expr::JsonHasAllKeys { expr, keys } => {
2058                let expr_sql = expr.build_with_dialect(dialect, params, offset);
2059                match dialect {
2060                    Dialect::Postgres => {
2061                        let keys_array = keys
2062                            .iter()
2063                            .map(|k| format!("'{k}'"))
2064                            .collect::<Vec<_>>()
2065                            .join(", ");
2066                        format!("{expr_sql} ?& array[{keys_array}]")
2067                    }
2068                    Dialect::Mysql => {
2069                        let paths = keys
2070                            .iter()
2071                            .map(|k| format!("'$.{k}'"))
2072                            .collect::<Vec<_>>()
2073                            .join(", ");
2074                        format!("JSON_CONTAINS_PATH({expr_sql}, 'all', {paths})")
2075                    }
2076                    Dialect::Sqlite => {
2077                        let checks = keys
2078                            .iter()
2079                            .map(|k| format!("json_type({expr_sql}, '$.{k}') IS NOT NULL"))
2080                            .collect::<Vec<_>>()
2081                            .join(" AND ");
2082                        format!("({checks})")
2083                    }
2084                }
2085            }
2086
2087            Expr::JsonArrayLength { expr } => {
2088                let expr_sql = expr.build_with_dialect(dialect, params, offset);
2089                match dialect {
2090                    Dialect::Postgres => format!("jsonb_array_length({expr_sql})"),
2091                    Dialect::Mysql => format!("JSON_LENGTH({expr_sql})"),
2092                    Dialect::Sqlite => format!("json_array_length({expr_sql})"),
2093                }
2094            }
2095
2096            Expr::JsonTypeof { expr } => {
2097                let expr_sql = expr.build_with_dialect(dialect, params, offset);
2098                match dialect {
2099                    Dialect::Postgres => format!("jsonb_typeof({expr_sql})"),
2100                    Dialect::Mysql => format!("JSON_TYPE({expr_sql})"),
2101                    Dialect::Sqlite => format!("json_type({expr_sql})"),
2102                }
2103            }
2104        }
2105    }
2106}
2107
2108// ==================== CASE Builder ====================
2109
2110/// Builder for CASE WHEN expressions.
2111#[derive(Debug, Clone)]
2112pub struct CaseBuilder {
2113    when_clauses: Vec<(Expr, Expr)>,
2114}
2115
2116impl CaseBuilder {
2117    /// Add a WHEN condition with its THEN result.
2118    pub fn when(mut self, condition: impl Into<Expr>, result: impl Into<Expr>) -> Self {
2119        self.when_clauses.push((condition.into(), result.into()));
2120        self
2121    }
2122
2123    /// Finalize with an ELSE clause (optional).
2124    pub fn otherwise(self, else_result: impl Into<Expr>) -> Expr {
2125        Expr::Case {
2126            when_clauses: self.when_clauses,
2127            else_clause: Some(Box::new(else_result.into())),
2128        }
2129    }
2130
2131    /// Finalize without an ELSE clause.
2132    pub fn end(self) -> Expr {
2133        Expr::Case {
2134            when_clauses: self.when_clauses,
2135            else_clause: None,
2136        }
2137    }
2138}
2139
2140// ==================== Window Builder ====================
2141
2142/// Builder for window functions with OVER clause.
2143#[derive(Debug, Clone)]
2144pub struct WindowBuilder {
2145    function: Expr,
2146    partition_by: Vec<Expr>,
2147    order_by: Vec<OrderBy>,
2148    frame: Option<WindowFrame>,
2149}
2150
2151impl WindowBuilder {
2152    /// Add a PARTITION BY expression.
2153    ///
2154    /// Can be called multiple times to partition by multiple columns.
2155    pub fn partition_by(mut self, expr: impl Into<Expr>) -> Self {
2156        self.partition_by.push(expr.into());
2157        self
2158    }
2159
2160    /// Add multiple PARTITION BY expressions at once.
2161    pub fn partition_by_many(mut self, exprs: Vec<impl Into<Expr>>) -> Self {
2162        self.partition_by.extend(exprs.into_iter().map(Into::into));
2163        self
2164    }
2165
2166    /// Add an ORDER BY clause within the window.
2167    ///
2168    /// Can be called multiple times to order by multiple columns.
2169    pub fn order_by(mut self, order: OrderBy) -> Self {
2170        self.order_by.push(order);
2171        self
2172    }
2173
2174    /// Add ORDER BY with ascending direction.
2175    pub fn order_by_asc(mut self, expr: impl Into<Expr>) -> Self {
2176        self.order_by.push(OrderBy {
2177            expr: expr.into(),
2178            direction: OrderDirection::Asc,
2179            nulls: None,
2180        });
2181        self
2182    }
2183
2184    /// Add ORDER BY with descending direction.
2185    pub fn order_by_desc(mut self, expr: impl Into<Expr>) -> Self {
2186        self.order_by.push(OrderBy {
2187            expr: expr.into(),
2188            direction: OrderDirection::Desc,
2189            nulls: None,
2190        });
2191        self
2192    }
2193
2194    /// Set frame specification: ROWS BETWEEN start AND end.
2195    ///
2196    /// # Example
2197    /// ```ignore
2198    /// // ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
2199    /// .rows_between(WindowFrameBound::Preceding(2), WindowFrameBound::CurrentRow)
2200    /// ```
2201    pub fn rows_between(mut self, start: WindowFrameBound, end: WindowFrameBound) -> Self {
2202        self.frame = Some(WindowFrame {
2203            frame_type: WindowFrameType::Rows,
2204            start,
2205            end: Some(end),
2206        });
2207        self
2208    }
2209
2210    /// Set frame specification: ROWS start (no end bound).
2211    ///
2212    /// # Example
2213    /// ```ignore
2214    /// // ROWS UNBOUNDED PRECEDING
2215    /// .rows(WindowFrameBound::UnboundedPreceding)
2216    /// ```
2217    pub fn rows(mut self, start: WindowFrameBound) -> Self {
2218        self.frame = Some(WindowFrame {
2219            frame_type: WindowFrameType::Rows,
2220            start,
2221            end: None,
2222        });
2223        self
2224    }
2225
2226    /// Set frame specification: RANGE BETWEEN start AND end.
2227    ///
2228    /// # Example
2229    /// ```ignore
2230    /// // RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
2231    /// .range_between(WindowFrameBound::UnboundedPreceding, WindowFrameBound::CurrentRow)
2232    /// ```
2233    pub fn range_between(mut self, start: WindowFrameBound, end: WindowFrameBound) -> Self {
2234        self.frame = Some(WindowFrame {
2235            frame_type: WindowFrameType::Range,
2236            start,
2237            end: Some(end),
2238        });
2239        self
2240    }
2241
2242    /// Set frame specification: RANGE start (no end bound).
2243    pub fn range(mut self, start: WindowFrameBound) -> Self {
2244        self.frame = Some(WindowFrame {
2245            frame_type: WindowFrameType::Range,
2246            start,
2247            end: None,
2248        });
2249        self
2250    }
2251
2252    /// Set frame specification: GROUPS BETWEEN start AND end (PostgreSQL 11+).
2253    pub fn groups_between(mut self, start: WindowFrameBound, end: WindowFrameBound) -> Self {
2254        self.frame = Some(WindowFrame {
2255            frame_type: WindowFrameType::Groups,
2256            start,
2257            end: Some(end),
2258        });
2259        self
2260    }
2261
2262    /// Finalize and build the window expression.
2263    pub fn build(self) -> Expr {
2264        Expr::Window {
2265            function: Box::new(self.function),
2266            partition_by: self.partition_by,
2267            order_by: self.order_by,
2268            frame: self.frame,
2269        }
2270    }
2271}
2272
2273// Conversion from Value to Expr
2274impl From<Value> for Expr {
2275    fn from(v: Value) -> Self {
2276        Expr::Literal(v)
2277    }
2278}
2279
2280impl From<&str> for Expr {
2281    fn from(s: &str) -> Self {
2282        Expr::Literal(Value::Text(s.to_string()))
2283    }
2284}
2285
2286impl From<String> for Expr {
2287    fn from(s: String) -> Self {
2288        Expr::Literal(Value::Text(s))
2289    }
2290}
2291
2292impl From<i32> for Expr {
2293    fn from(n: i32) -> Self {
2294        Expr::Literal(Value::Int(n))
2295    }
2296}
2297
2298impl From<i64> for Expr {
2299    fn from(n: i64) -> Self {
2300        Expr::Literal(Value::BigInt(n))
2301    }
2302}
2303
2304impl From<bool> for Expr {
2305    fn from(b: bool) -> Self {
2306        Expr::Literal(Value::Bool(b))
2307    }
2308}
2309
2310impl From<f64> for Expr {
2311    fn from(n: f64) -> Self {
2312        Expr::Literal(Value::Double(n))
2313    }
2314}
2315
2316impl From<f32> for Expr {
2317    fn from(n: f32) -> Self {
2318        Expr::Literal(Value::Float(n))
2319    }
2320}
2321
2322// ==================== Helper Functions ====================
2323
2324/// Adjust placeholder indices in a SQL string.
2325///
2326/// Rewrites $1, $2, etc. to $offset+1, $offset+2, etc. for PostgreSQL,
2327/// or ?1, ?2, etc. for SQLite. MySQL always uses ? so no adjustment needed.
2328fn adjust_placeholder_indices(sql: &str, offset: usize, dialect: Dialect) -> String {
2329    if offset == 0 {
2330        return sql.to_string();
2331    }
2332
2333    match dialect {
2334        Dialect::Postgres => {
2335            // Rewrite $N to $(N+offset)
2336            let mut result = String::with_capacity(sql.len() + 20);
2337            let mut chars = sql.chars().peekable();
2338
2339            while let Some(c) = chars.next() {
2340                if c == '$' {
2341                    // Collect digits after $
2342                    let mut num_str = String::new();
2343                    while let Some(&d) = chars.peek() {
2344                        if d.is_ascii_digit() {
2345                            num_str.push(chars.next().unwrap());
2346                        } else {
2347                            break;
2348                        }
2349                    }
2350                    if let Ok(n) = num_str.parse::<usize>() {
2351                        result.push_str(&format!("${}", n + offset));
2352                    } else {
2353                        result.push('$');
2354                        result.push_str(&num_str);
2355                    }
2356                } else {
2357                    result.push(c);
2358                }
2359            }
2360            result
2361        }
2362        Dialect::Sqlite => {
2363            // Rewrite ?N to ?(N+offset)
2364            let mut result = String::with_capacity(sql.len() + 20);
2365            let mut chars = sql.chars().peekable();
2366
2367            while let Some(c) = chars.next() {
2368                if c == '?' {
2369                    // Collect digits after ?
2370                    let mut num_str = String::new();
2371                    while let Some(&d) = chars.peek() {
2372                        if d.is_ascii_digit() {
2373                            num_str.push(chars.next().unwrap());
2374                        } else {
2375                            break;
2376                        }
2377                    }
2378                    if let Ok(n) = num_str.parse::<usize>() {
2379                        result.push_str(&format!("?{}", n + offset));
2380                    } else {
2381                        result.push('?');
2382                        result.push_str(&num_str);
2383                    }
2384                } else {
2385                    result.push(c);
2386                }
2387            }
2388            result
2389        }
2390        Dialect::Mysql => {
2391            // MySQL uses ? without indices, so no adjustment needed
2392            sql.to_string()
2393        }
2394    }
2395}
2396
2397// ==================== Tests ====================
2398
2399#[cfg(test)]
2400mod tests {
2401    use super::*;
2402
2403    // ==================== Column Tests ====================
2404
2405    #[test]
2406    fn test_column_simple() {
2407        let expr = Expr::col("name");
2408        let mut params = Vec::new();
2409        let sql = expr.build(&mut params, 0);
2410        assert_eq!(sql, "\"name\"");
2411        assert!(params.is_empty());
2412    }
2413
2414    #[test]
2415    fn test_column_qualified() {
2416        let expr = Expr::qualified("users", "name");
2417        let mut params = Vec::new();
2418        let sql = expr.build(&mut params, 0);
2419        assert_eq!(sql, "\"users\".\"name\"");
2420        assert!(params.is_empty());
2421    }
2422
2423    // ==================== Literal Tests ====================
2424
2425    #[test]
2426    fn test_literal_int() {
2427        let expr = Expr::lit(42);
2428        let mut params = Vec::new();
2429        let sql = expr.build(&mut params, 0);
2430        assert_eq!(sql, "$1");
2431        assert_eq!(params.len(), 1);
2432        assert_eq!(params[0], Value::Int(42));
2433    }
2434
2435    #[test]
2436    fn test_literal_string() {
2437        let expr = Expr::lit("hello");
2438        let mut params = Vec::new();
2439        let sql = expr.build(&mut params, 0);
2440        assert_eq!(sql, "$1");
2441        assert_eq!(params[0], Value::Text("hello".to_string()));
2442    }
2443
2444    #[test]
2445    fn test_literal_null() {
2446        let expr = Expr::null();
2447        let mut params = Vec::new();
2448        let sql = expr.build(&mut params, 0);
2449        assert_eq!(sql, "$1");
2450        assert_eq!(params[0], Value::Null);
2451    }
2452
2453    // ==================== Comparison Tests ====================
2454
2455    #[test]
2456    fn test_eq() {
2457        let expr = Expr::col("age").eq(18);
2458        let mut params = Vec::new();
2459        let sql = expr.build(&mut params, 0);
2460        assert_eq!(sql, "\"age\" = $1");
2461        assert_eq!(params[0], Value::Int(18));
2462    }
2463
2464    #[test]
2465    fn test_ne() {
2466        let expr = Expr::col("status").ne("deleted");
2467        let mut params = Vec::new();
2468        let sql = expr.build(&mut params, 0);
2469        assert_eq!(sql, "\"status\" <> $1");
2470    }
2471
2472    #[test]
2473    fn test_lt_le_gt_ge() {
2474        let mut params = Vec::new();
2475
2476        let lt = Expr::col("age").lt(18).build(&mut params, 0);
2477        assert_eq!(lt, "\"age\" < $1");
2478
2479        params.clear();
2480        let le = Expr::col("age").le(18).build(&mut params, 0);
2481        assert_eq!(le, "\"age\" <= $1");
2482
2483        params.clear();
2484        let gt = Expr::col("age").gt(18).build(&mut params, 0);
2485        assert_eq!(gt, "\"age\" > $1");
2486
2487        params.clear();
2488        let ge = Expr::col("age").ge(18).build(&mut params, 0);
2489        assert_eq!(ge, "\"age\" >= $1");
2490    }
2491
2492    // ==================== Logical Tests ====================
2493
2494    #[test]
2495    fn test_and() {
2496        let expr = Expr::col("a").eq(1).and(Expr::col("b").eq(2));
2497        let mut params = Vec::new();
2498        let sql = expr.build(&mut params, 0);
2499        assert_eq!(sql, "\"a\" = $1 AND \"b\" = $2");
2500    }
2501
2502    #[test]
2503    fn test_or() {
2504        let expr = Expr::col("a").eq(1).or(Expr::col("b").eq(2));
2505        let mut params = Vec::new();
2506        let sql = expr.build(&mut params, 0);
2507        assert_eq!(sql, "\"a\" = $1 OR \"b\" = $2");
2508    }
2509
2510    #[test]
2511    fn test_not() {
2512        let expr = Expr::col("active").not();
2513        let mut params = Vec::new();
2514        let sql = expr.build(&mut params, 0);
2515        assert_eq!(sql, "NOT \"active\"");
2516    }
2517
2518    // ==================== Null Tests ====================
2519
2520    #[test]
2521    fn test_is_null() {
2522        let expr = Expr::col("deleted_at").is_null();
2523        let mut params = Vec::new();
2524        let sql = expr.build(&mut params, 0);
2525        assert_eq!(sql, "\"deleted_at\" IS NULL");
2526    }
2527
2528    #[test]
2529    fn test_is_not_null() {
2530        let expr = Expr::col("name").is_not_null();
2531        let mut params = Vec::new();
2532        let sql = expr.build(&mut params, 0);
2533        assert_eq!(sql, "\"name\" IS NOT NULL");
2534    }
2535
2536    // ==================== Pattern Matching Tests ====================
2537
2538    #[test]
2539    fn test_like() {
2540        let expr = Expr::col("name").like("%john%");
2541        let mut params = Vec::new();
2542        let sql = expr.build(&mut params, 0);
2543        assert_eq!(sql, "\"name\" LIKE $1");
2544        assert_eq!(params[0], Value::Text("%john%".to_string()));
2545    }
2546
2547    #[test]
2548    fn test_not_like() {
2549        let expr = Expr::col("name").not_like("%test%");
2550        let mut params = Vec::new();
2551        let sql = expr.build(&mut params, 0);
2552        assert_eq!(sql, "\"name\" NOT LIKE $1");
2553    }
2554
2555    #[test]
2556    fn test_ilike_postgres() {
2557        let expr = Expr::col("name").ilike("%JOHN%");
2558        let mut params = Vec::new();
2559        let sql = expr.build_with_dialect(Dialect::Postgres, &mut params, 0);
2560        assert_eq!(sql, "\"name\" ILIKE $1");
2561    }
2562
2563    #[test]
2564    fn test_ilike_fallback_sqlite() {
2565        let expr = Expr::col("name").ilike("%JOHN%");
2566        let mut params = Vec::new();
2567        let sql = expr.build_with_dialect(Dialect::Sqlite, &mut params, 0);
2568        assert_eq!(sql, "LOWER(\"name\") LIKE LOWER(?1)");
2569    }
2570
2571    // ==================== IN Tests ====================
2572
2573    #[test]
2574    fn test_in_list() {
2575        let expr = Expr::col("status").in_list(vec![1, 2, 3]);
2576        let mut params = Vec::new();
2577        let sql = expr.build(&mut params, 0);
2578        assert_eq!(sql, "\"status\" IN ($1, $2, $3)");
2579        assert_eq!(params.len(), 3);
2580    }
2581
2582    #[test]
2583    fn test_not_in_list() {
2584        let expr = Expr::col("status").not_in_list(vec![4, 5]);
2585        let mut params = Vec::new();
2586        let sql = expr.build(&mut params, 0);
2587        assert_eq!(sql, "\"status\" NOT IN ($1, $2)");
2588    }
2589
2590    // ==================== BETWEEN Tests ====================
2591
2592    #[test]
2593    fn test_between() {
2594        let expr = Expr::col("age").between(18, 65);
2595        let mut params = Vec::new();
2596        let sql = expr.build(&mut params, 0);
2597        assert_eq!(sql, "\"age\" BETWEEN $1 AND $2");
2598        assert_eq!(params[0], Value::Int(18));
2599        assert_eq!(params[1], Value::Int(65));
2600    }
2601
2602    #[test]
2603    fn test_not_between() {
2604        let expr = Expr::col("age").not_between(0, 17);
2605        let mut params = Vec::new();
2606        let sql = expr.build(&mut params, 0);
2607        assert_eq!(sql, "\"age\" NOT BETWEEN $1 AND $2");
2608    }
2609
2610    // ==================== Arithmetic Tests ====================
2611
2612    #[test]
2613    fn test_arithmetic() {
2614        let mut params = Vec::new();
2615
2616        let add = Expr::col("a").add(Expr::col("b")).build(&mut params, 0);
2617        assert_eq!(add, "\"a\" + \"b\"");
2618
2619        let sub = Expr::col("a").sub(Expr::col("b")).build(&mut params, 0);
2620        assert_eq!(sub, "\"a\" - \"b\"");
2621
2622        let mul = Expr::col("a").mul(Expr::col("b")).build(&mut params, 0);
2623        assert_eq!(mul, "\"a\" * \"b\"");
2624
2625        let div = Expr::col("a").div(Expr::col("b")).build(&mut params, 0);
2626        assert_eq!(div, "\"a\" / \"b\"");
2627
2628        let modulo = Expr::col("a").modulo(Expr::col("b")).build(&mut params, 0);
2629        assert_eq!(modulo, "\"a\" % \"b\"");
2630    }
2631
2632    #[test]
2633    fn test_neg() {
2634        let expr = Expr::col("balance").neg();
2635        let mut params = Vec::new();
2636        let sql = expr.build(&mut params, 0);
2637        assert_eq!(sql, "-\"balance\"");
2638    }
2639
2640    // ==================== Bitwise Tests ====================
2641
2642    #[test]
2643    fn test_bitwise() {
2644        let mut params = Vec::new();
2645
2646        let bit_and = Expr::col("flags")
2647            .bit_and(Expr::lit(0xFF))
2648            .build(&mut params, 0);
2649        assert_eq!(bit_and, "\"flags\" & $1");
2650
2651        params.clear();
2652        let or_sql = Expr::col("flags")
2653            .bit_or(Expr::lit(0x01))
2654            .build(&mut params, 0);
2655        assert_eq!(or_sql, "\"flags\" | $1");
2656
2657        params.clear();
2658        let xor_sql = Expr::col("flags")
2659            .bit_xor(Expr::lit(0x0F))
2660            .build(&mut params, 0);
2661        assert_eq!(xor_sql, "\"flags\" ^ $1");
2662
2663        let bit_not = Expr::col("flags").bit_not().build(&mut params, 0);
2664        assert_eq!(bit_not, "~\"flags\"");
2665    }
2666
2667    // ==================== CASE Tests ====================
2668
2669    #[test]
2670    fn test_case_simple() {
2671        let expr = Expr::case()
2672            .when(Expr::col("status").eq("active"), "Yes")
2673            .when(Expr::col("status").eq("pending"), "Maybe")
2674            .otherwise("No");
2675
2676        let mut params = Vec::new();
2677        let sql = expr.build(&mut params, 0);
2678        assert_eq!(
2679            sql,
2680            "CASE WHEN \"status\" = $1 THEN $2 WHEN \"status\" = $3 THEN $4 ELSE $5 END"
2681        );
2682        assert_eq!(params.len(), 5);
2683    }
2684
2685    #[test]
2686    fn test_case_without_else() {
2687        let expr = Expr::case().when(Expr::col("age").gt(18), "adult").end();
2688
2689        let mut params = Vec::new();
2690        let sql = expr.build(&mut params, 0);
2691        assert_eq!(sql, "CASE WHEN \"age\" > $1 THEN $2 END");
2692    }
2693
2694    // ==================== Aggregate Tests ====================
2695
2696    #[test]
2697    fn test_count_star() {
2698        let expr = Expr::count_star();
2699        let mut params = Vec::new();
2700        let sql = expr.build(&mut params, 0);
2701        assert_eq!(sql, "COUNT(*)");
2702    }
2703
2704    #[test]
2705    fn test_count() {
2706        let expr = Expr::col("id").count();
2707        let mut params = Vec::new();
2708        let sql = expr.build(&mut params, 0);
2709        assert_eq!(sql, "COUNT(\"id\")");
2710    }
2711
2712    #[test]
2713    fn test_aggregates() {
2714        let mut params = Vec::new();
2715
2716        let sum = Expr::col("amount").sum().build(&mut params, 0);
2717        assert_eq!(sum, "SUM(\"amount\")");
2718
2719        let avg = Expr::col("price").avg().build(&mut params, 0);
2720        assert_eq!(avg, "AVG(\"price\")");
2721
2722        let min = Expr::col("age").min().build(&mut params, 0);
2723        assert_eq!(min, "MIN(\"age\")");
2724
2725        let max = Expr::col("score").max().build(&mut params, 0);
2726        assert_eq!(max, "MAX(\"score\")");
2727    }
2728
2729    // ==================== Function Tests ====================
2730
2731    #[test]
2732    fn test_function() {
2733        let expr = Expr::function("UPPER", vec![Expr::col("name")]);
2734        let mut params = Vec::new();
2735        let sql = expr.build(&mut params, 0);
2736        assert_eq!(sql, "UPPER(\"name\")");
2737    }
2738
2739    #[test]
2740    fn test_function_multiple_args() {
2741        let expr = Expr::function("COALESCE", vec![Expr::col("name"), Expr::lit("Unknown")]);
2742        let mut params = Vec::new();
2743        let sql = expr.build(&mut params, 0);
2744        assert_eq!(sql, "COALESCE(\"name\", $1)");
2745    }
2746
2747    // ==================== Order Expression Tests ====================
2748
2749    #[test]
2750    fn test_order_asc() {
2751        let order = Expr::col("name").asc();
2752        let mut params = Vec::new();
2753        let sql = order.build(Dialect::Postgres, &mut params, 0);
2754        assert_eq!(sql, "\"name\" ASC");
2755    }
2756
2757    #[test]
2758    fn test_order_desc() {
2759        let order = Expr::col("created_at").desc();
2760        let mut params = Vec::new();
2761        let sql = order.build(Dialect::Postgres, &mut params, 0);
2762        assert_eq!(sql, "\"created_at\" DESC");
2763    }
2764
2765    #[test]
2766    fn test_order_nulls() {
2767        let order_first = Expr::col("name").asc().nulls_first();
2768        let mut params = Vec::new();
2769        let sql = order_first.build(Dialect::Postgres, &mut params, 0);
2770        assert_eq!(sql, "\"name\" ASC NULLS FIRST");
2771
2772        let order_last = Expr::col("name").desc().nulls_last();
2773        let sql = order_last.build(Dialect::Postgres, &mut params, 0);
2774        assert_eq!(sql, "\"name\" DESC NULLS LAST");
2775    }
2776
2777    // ==================== Dialect Tests ====================
2778
2779    #[test]
2780    fn test_dialect_postgres() {
2781        let expr = Expr::col("id").eq(1);
2782        let mut params = Vec::new();
2783        let sql = expr.build_with_dialect(Dialect::Postgres, &mut params, 0);
2784        assert_eq!(sql, "\"id\" = $1");
2785    }
2786
2787    #[test]
2788    fn test_dialect_sqlite() {
2789        let expr = Expr::col("id").eq(1);
2790        let mut params = Vec::new();
2791        let sql = expr.build_with_dialect(Dialect::Sqlite, &mut params, 0);
2792        assert_eq!(sql, "\"id\" = ?1");
2793    }
2794
2795    #[test]
2796    fn test_dialect_mysql() {
2797        let expr = Expr::col("id").eq(1);
2798        let mut params = Vec::new();
2799        let sql = expr.build_with_dialect(Dialect::Mysql, &mut params, 0);
2800        assert_eq!(sql, "`id` = ?");
2801    }
2802
2803    // ==================== Complex Expression Tests ====================
2804
2805    #[test]
2806    fn test_complex_nested() {
2807        // (age > 18 AND status = 'active') OR is_admin = true
2808        let expr = Expr::col("age")
2809            .gt(18)
2810            .and(Expr::col("status").eq("active"))
2811            .paren()
2812            .or(Expr::col("is_admin").eq(true));
2813
2814        let mut params = Vec::new();
2815        let sql = expr.build(&mut params, 0);
2816        assert_eq!(
2817            sql,
2818            "(\"age\" > $1 AND \"status\" = $2) OR \"is_admin\" = $3"
2819        );
2820    }
2821
2822    #[test]
2823    fn test_parameter_offset() {
2824        let expr = Expr::col("name").eq("test");
2825        let mut params = Vec::new();
2826        let sql = expr.build(&mut params, 5);
2827        assert_eq!(sql, "\"name\" = $6");
2828    }
2829
2830    // ==================== String Concat Tests ====================
2831
2832    #[test]
2833    fn test_concat() {
2834        let expr = Expr::col("first_name")
2835            .concat(" ")
2836            .concat(Expr::col("last_name"));
2837        let mut params = Vec::new();
2838        let sql = expr.build(&mut params, 0);
2839        assert_eq!(sql, "\"first_name\" || $1 || \"last_name\"");
2840    }
2841
2842    // ==================== Placeholder Tests ====================
2843
2844    #[test]
2845    fn test_placeholder() {
2846        let expr = Expr::col("id").eq(Expr::placeholder(1));
2847        let mut params = Vec::new();
2848        let sql = expr.build(&mut params, 0);
2849        assert_eq!(sql, "\"id\" = $1");
2850        assert!(params.is_empty()); // Placeholder doesn't add to params
2851    }
2852
2853    // ==================== Subquery Tests ====================
2854
2855    #[test]
2856    fn test_subquery() {
2857        let expr = Expr::col("dept_id").in_list(vec![Expr::subquery(
2858            "SELECT id FROM departments WHERE active = true",
2859        )]);
2860        let mut params = Vec::new();
2861        let sql = expr.build(&mut params, 0);
2862        assert_eq!(
2863            sql,
2864            "\"dept_id\" IN ((SELECT id FROM departments WHERE active = true))"
2865        );
2866    }
2867
2868    // ==================== Raw SQL Tests ====================
2869
2870    #[test]
2871    fn test_raw() {
2872        let expr = Expr::raw("NOW()");
2873        let mut params = Vec::new();
2874        let sql = expr.build(&mut params, 0);
2875        assert_eq!(sql, "NOW()");
2876    }
2877
2878    // ==================== Precedence Tests ====================
2879
2880    #[test]
2881    fn test_precedence() {
2882        assert!(BinaryOp::Mul.precedence() > BinaryOp::Add.precedence());
2883        assert!(BinaryOp::And.precedence() > BinaryOp::Or.precedence());
2884        assert!(BinaryOp::Eq.precedence() > BinaryOp::And.precedence());
2885    }
2886
2887    // ==================== Quote Escaping Tests ====================
2888
2889    #[test]
2890    fn test_quote_identifier_escapes_postgres() {
2891        // Postgres/SQLite: double-quotes must be escaped by doubling
2892        assert_eq!(Dialect::Postgres.quote_identifier("simple"), "\"simple\"");
2893        assert_eq!(
2894            Dialect::Postgres.quote_identifier("with\"quote"),
2895            "\"with\"\"quote\""
2896        );
2897        assert_eq!(
2898            Dialect::Postgres.quote_identifier("multi\"\"quotes"),
2899            "\"multi\"\"\"\"quotes\""
2900        );
2901    }
2902
2903    #[test]
2904    fn test_quote_identifier_escapes_sqlite() {
2905        // SQLite also uses double-quotes
2906        assert_eq!(Dialect::Sqlite.quote_identifier("simple"), "\"simple\"");
2907        assert_eq!(
2908            Dialect::Sqlite.quote_identifier("with\"quote"),
2909            "\"with\"\"quote\""
2910        );
2911    }
2912
2913    #[test]
2914    fn test_quote_identifier_escapes_mysql() {
2915        // MySQL: backticks must be escaped by doubling
2916        assert_eq!(Dialect::Mysql.quote_identifier("simple"), "`simple`");
2917        assert_eq!(
2918            Dialect::Mysql.quote_identifier("with`backtick"),
2919            "`with``backtick`"
2920        );
2921        assert_eq!(
2922            Dialect::Mysql.quote_identifier("multi``ticks"),
2923            "`multi````ticks`"
2924        );
2925    }
2926
2927    // ==================== Window Function Tests ====================
2928
2929    #[test]
2930    fn test_window_row_number_empty_over() {
2931        let expr = Expr::row_number().over().build();
2932        let mut params = Vec::new();
2933        let sql = expr.build(&mut params, 0);
2934        assert_eq!(sql, "ROW_NUMBER() OVER ()");
2935    }
2936
2937    #[test]
2938    fn test_window_row_number_order_by() {
2939        let expr = Expr::row_number()
2940            .over()
2941            .order_by_desc(Expr::col("created_at"))
2942            .build();
2943        let mut params = Vec::new();
2944        let sql = expr.build(&mut params, 0);
2945        assert_eq!(sql, "ROW_NUMBER() OVER (ORDER BY \"created_at\" DESC)");
2946    }
2947
2948    #[test]
2949    fn test_window_partition_by() {
2950        let expr = Expr::row_number()
2951            .over()
2952            .partition_by(Expr::col("department"))
2953            .order_by_asc(Expr::col("hire_date"))
2954            .build();
2955        let mut params = Vec::new();
2956        let sql = expr.build(&mut params, 0);
2957        assert_eq!(
2958            sql,
2959            "ROW_NUMBER() OVER (PARTITION BY \"department\" ORDER BY \"hire_date\" ASC)"
2960        );
2961    }
2962
2963    #[test]
2964    fn test_window_multiple_partition_by() {
2965        let expr = Expr::rank()
2966            .over()
2967            .partition_by(Expr::col("region"))
2968            .partition_by(Expr::col("product"))
2969            .order_by_desc(Expr::col("sales"))
2970            .build();
2971        let mut params = Vec::new();
2972        let sql = expr.build(&mut params, 0);
2973        assert_eq!(
2974            sql,
2975            "RANK() OVER (PARTITION BY \"region\", \"product\" ORDER BY \"sales\" DESC)"
2976        );
2977    }
2978
2979    #[test]
2980    fn test_window_dense_rank() {
2981        let expr = Expr::dense_rank()
2982            .over()
2983            .order_by_asc(Expr::col("score"))
2984            .build();
2985        let mut params = Vec::new();
2986        let sql = expr.build(&mut params, 0);
2987        assert_eq!(sql, "DENSE_RANK() OVER (ORDER BY \"score\" ASC)");
2988    }
2989
2990    #[test]
2991    fn test_window_ntile() {
2992        let expr = Expr::ntile(4)
2993            .over()
2994            .order_by_asc(Expr::col("salary"))
2995            .build();
2996        let mut params = Vec::new();
2997        let sql = expr.build(&mut params, 0);
2998        assert_eq!(sql, "NTILE($1) OVER (ORDER BY \"salary\" ASC)");
2999        assert_eq!(params[0], Value::BigInt(4));
3000    }
3001
3002    #[test]
3003    fn test_window_lag() {
3004        let expr = Expr::col("price")
3005            .lag()
3006            .over()
3007            .order_by_asc(Expr::col("date"))
3008            .build();
3009        let mut params = Vec::new();
3010        let sql = expr.build(&mut params, 0);
3011        assert_eq!(sql, "LAG(\"price\") OVER (ORDER BY \"date\" ASC)");
3012    }
3013
3014    #[test]
3015    fn test_window_lag_with_offset() {
3016        let expr = Expr::col("price")
3017            .lag_offset(3)
3018            .over()
3019            .order_by_asc(Expr::col("date"))
3020            .build();
3021        let mut params = Vec::new();
3022        let sql = expr.build(&mut params, 0);
3023        assert_eq!(sql, "LAG(\"price\", $1) OVER (ORDER BY \"date\" ASC)");
3024        assert_eq!(params[0], Value::BigInt(3));
3025    }
3026
3027    #[test]
3028    fn test_window_lead_with_default() {
3029        let expr = Expr::col("price")
3030            .lead_with_default(1, 0)
3031            .over()
3032            .order_by_asc(Expr::col("date"))
3033            .build();
3034        let mut params = Vec::new();
3035        let sql = expr.build(&mut params, 0);
3036        assert_eq!(sql, "LEAD(\"price\", $1, $2) OVER (ORDER BY \"date\" ASC)");
3037        assert_eq!(params[0], Value::BigInt(1));
3038        assert_eq!(params[1], Value::Int(0));
3039    }
3040
3041    #[test]
3042    fn test_window_first_value() {
3043        let expr = Expr::col("salary")
3044            .first_value()
3045            .over()
3046            .partition_by(Expr::col("department"))
3047            .order_by_desc(Expr::col("salary"))
3048            .build();
3049        let mut params = Vec::new();
3050        let sql = expr.build(&mut params, 0);
3051        assert_eq!(
3052            sql,
3053            "FIRST_VALUE(\"salary\") OVER (PARTITION BY \"department\" ORDER BY \"salary\" DESC)"
3054        );
3055    }
3056
3057    #[test]
3058    fn test_window_last_value() {
3059        let expr = Expr::col("amount")
3060            .last_value()
3061            .over()
3062            .order_by_asc(Expr::col("created_at"))
3063            .build();
3064        let mut params = Vec::new();
3065        let sql = expr.build(&mut params, 0);
3066        assert_eq!(
3067            sql,
3068            "LAST_VALUE(\"amount\") OVER (ORDER BY \"created_at\" ASC)"
3069        );
3070    }
3071
3072    #[test]
3073    fn test_window_nth_value() {
3074        let expr = Expr::col("score")
3075            .nth_value(3)
3076            .over()
3077            .order_by_desc(Expr::col("score"))
3078            .build();
3079        let mut params = Vec::new();
3080        let sql = expr.build(&mut params, 0);
3081        assert_eq!(
3082            sql,
3083            "NTH_VALUE(\"score\", $1) OVER (ORDER BY \"score\" DESC)"
3084        );
3085        assert_eq!(params[0], Value::BigInt(3));
3086    }
3087
3088    #[test]
3089    fn test_window_aggregate_sum() {
3090        let expr = Expr::col("amount")
3091            .sum()
3092            .over()
3093            .partition_by(Expr::col("customer_id"))
3094            .build();
3095        let mut params = Vec::new();
3096        let sql = expr.build(&mut params, 0);
3097        assert_eq!(sql, "SUM(\"amount\") OVER (PARTITION BY \"customer_id\")");
3098    }
3099
3100    #[test]
3101    fn test_window_aggregate_avg_running() {
3102        let expr = Expr::col("price")
3103            .avg()
3104            .over()
3105            .order_by_asc(Expr::col("date"))
3106            .rows_between(
3107                WindowFrameBound::UnboundedPreceding,
3108                WindowFrameBound::CurrentRow,
3109            )
3110            .build();
3111        let mut params = Vec::new();
3112        let sql = expr.build(&mut params, 0);
3113        assert_eq!(
3114            sql,
3115            "AVG(\"price\") OVER (ORDER BY \"date\" ASC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)"
3116        );
3117    }
3118
3119    #[test]
3120    fn test_window_frame_rows_preceding() {
3121        let expr = Expr::col("value")
3122            .sum()
3123            .over()
3124            .order_by_asc(Expr::col("idx"))
3125            .rows_between(WindowFrameBound::Preceding(2), WindowFrameBound::CurrentRow)
3126            .build();
3127        let mut params = Vec::new();
3128        let sql = expr.build(&mut params, 0);
3129        assert_eq!(
3130            sql,
3131            "SUM(\"value\") OVER (ORDER BY \"idx\" ASC ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)"
3132        );
3133    }
3134
3135    #[test]
3136    fn test_window_frame_rows_following() {
3137        let expr = Expr::col("value")
3138            .avg()
3139            .over()
3140            .order_by_asc(Expr::col("idx"))
3141            .rows_between(WindowFrameBound::CurrentRow, WindowFrameBound::Following(3))
3142            .build();
3143        let mut params = Vec::new();
3144        let sql = expr.build(&mut params, 0);
3145        assert_eq!(
3146            sql,
3147            "AVG(\"value\") OVER (ORDER BY \"idx\" ASC ROWS BETWEEN CURRENT ROW AND 3 FOLLOWING)"
3148        );
3149    }
3150
3151    #[test]
3152    fn test_window_frame_range_unbounded() {
3153        let expr = Expr::col("total")
3154            .sum()
3155            .over()
3156            .partition_by(Expr::col("category"))
3157            .order_by_asc(Expr::col("date"))
3158            .range_between(
3159                WindowFrameBound::UnboundedPreceding,
3160                WindowFrameBound::UnboundedFollowing,
3161            )
3162            .build();
3163        let mut params = Vec::new();
3164        let sql = expr.build(&mut params, 0);
3165        assert_eq!(
3166            sql,
3167            "SUM(\"total\") OVER (PARTITION BY \"category\" ORDER BY \"date\" ASC RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)"
3168        );
3169    }
3170
3171    #[test]
3172    fn test_window_frame_rows_single_bound() {
3173        let expr = Expr::col("value")
3174            .sum()
3175            .over()
3176            .order_by_asc(Expr::col("idx"))
3177            .rows(WindowFrameBound::UnboundedPreceding)
3178            .build();
3179        let mut params = Vec::new();
3180        let sql = expr.build(&mut params, 0);
3181        assert_eq!(
3182            sql,
3183            "SUM(\"value\") OVER (ORDER BY \"idx\" ASC ROWS UNBOUNDED PRECEDING)"
3184        );
3185    }
3186
3187    #[test]
3188    fn test_window_percent_rank() {
3189        let expr = Expr::percent_rank()
3190            .over()
3191            .order_by_asc(Expr::col("score"))
3192            .build();
3193        let mut params = Vec::new();
3194        let sql = expr.build(&mut params, 0);
3195        assert_eq!(sql, "PERCENT_RANK() OVER (ORDER BY \"score\" ASC)");
3196    }
3197
3198    #[test]
3199    fn test_window_cume_dist() {
3200        let expr = Expr::cume_dist()
3201            .over()
3202            .partition_by(Expr::col("group_id"))
3203            .order_by_asc(Expr::col("value"))
3204            .build();
3205        let mut params = Vec::new();
3206        let sql = expr.build(&mut params, 0);
3207        assert_eq!(
3208            sql,
3209            "CUME_DIST() OVER (PARTITION BY \"group_id\" ORDER BY \"value\" ASC)"
3210        );
3211    }
3212
3213    #[test]
3214    fn test_window_frame_groups() {
3215        let expr = Expr::col("amount")
3216            .sum()
3217            .over()
3218            .order_by_asc(Expr::col("group_rank"))
3219            .groups_between(
3220                WindowFrameBound::Preceding(1),
3221                WindowFrameBound::Following(1),
3222            )
3223            .build();
3224        let mut params = Vec::new();
3225        let sql = expr.build(&mut params, 0);
3226        assert_eq!(
3227            sql,
3228            "SUM(\"amount\") OVER (ORDER BY \"group_rank\" ASC GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING)"
3229        );
3230    }
3231
3232    // ==================== EXISTS Tests ====================
3233
3234    #[test]
3235    fn test_exists_basic() {
3236        // EXISTS (SELECT 1 FROM orders WHERE orders.customer_id = customers.id)
3237        let expr = Expr::exists(
3238            "SELECT 1 FROM orders WHERE orders.customer_id = customers.id",
3239            vec![],
3240        );
3241        let mut params = Vec::new();
3242        let sql = expr.build(&mut params, 0);
3243        assert_eq!(
3244            sql,
3245            "EXISTS (SELECT 1 FROM orders WHERE orders.customer_id = customers.id)"
3246        );
3247        assert!(params.is_empty());
3248    }
3249
3250    #[test]
3251    fn test_not_exists() {
3252        // NOT EXISTS (SELECT 1 FROM orders WHERE orders.customer_id = customers.id)
3253        let expr = Expr::not_exists(
3254            "SELECT 1 FROM orders WHERE orders.customer_id = customers.id",
3255            vec![],
3256        );
3257        let mut params = Vec::new();
3258        let sql = expr.build(&mut params, 0);
3259        assert_eq!(
3260            sql,
3261            "NOT EXISTS (SELECT 1 FROM orders WHERE orders.customer_id = customers.id)"
3262        );
3263        assert!(params.is_empty());
3264    }
3265
3266    #[test]
3267    fn test_exists_with_params() {
3268        // EXISTS (SELECT 1 FROM orders WHERE status = $1)
3269        let expr = Expr::exists(
3270            "SELECT 1 FROM orders WHERE status = $1",
3271            vec![Value::Text("active".to_string())],
3272        );
3273        let mut params = Vec::new();
3274        let sql = expr.build(&mut params, 0);
3275        assert_eq!(sql, "EXISTS (SELECT 1 FROM orders WHERE status = $1)");
3276        assert_eq!(params.len(), 1);
3277        assert_eq!(params[0], Value::Text("active".to_string()));
3278    }
3279
3280    #[test]
3281    fn test_exists_with_params_offset() {
3282        // When there's an offset, placeholder indices should be adjusted
3283        let expr = Expr::exists(
3284            "SELECT 1 FROM orders WHERE status = $1 AND type = $2",
3285            vec![
3286                Value::Text("active".to_string()),
3287                Value::Text("online".to_string()),
3288            ],
3289        );
3290        let mut params = Vec::new();
3291        // Simulate offset of 3 (meaning params $1-$3 are already used by outer query)
3292        let sql = expr.build(&mut params, 3);
3293        assert_eq!(
3294            sql,
3295            "EXISTS (SELECT 1 FROM orders WHERE status = $4 AND type = $5)"
3296        );
3297        assert_eq!(params.len(), 2);
3298    }
3299
3300    #[test]
3301    fn test_exists_in_where_clause() {
3302        // Combining EXISTS with AND
3303        let exists_expr = Expr::exists("SELECT 1 FROM orders o WHERE o.customer_id = c.id", vec![]);
3304        let expr = Expr::col("active").eq(true).and(exists_expr);
3305        let mut params = Vec::new();
3306        let sql = expr.build(&mut params, 0);
3307        assert_eq!(
3308            sql,
3309            "\"active\" = $1 AND EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id)"
3310        );
3311        assert_eq!(params[0], Value::Bool(true));
3312    }
3313
3314    #[test]
3315    fn test_exists_sqlite_dialect() {
3316        let expr = Expr::exists(
3317            "SELECT 1 FROM orders WHERE status = ?1",
3318            vec![Value::Text("active".to_string())],
3319        );
3320        let mut params = Vec::new();
3321        let sql = expr.build_with_dialect(Dialect::Sqlite, &mut params, 0);
3322        assert_eq!(sql, "EXISTS (SELECT 1 FROM orders WHERE status = ?1)");
3323        assert_eq!(params.len(), 1);
3324    }
3325
3326    #[test]
3327    fn test_exists_sqlite_with_offset() {
3328        let expr = Expr::exists(
3329            "SELECT 1 FROM orders WHERE status = ?1",
3330            vec![Value::Text("active".to_string())],
3331        );
3332        let mut params = Vec::new();
3333        let sql = expr.build_with_dialect(Dialect::Sqlite, &mut params, 2);
3334        assert_eq!(sql, "EXISTS (SELECT 1 FROM orders WHERE status = ?3)");
3335        assert_eq!(params.len(), 1);
3336    }
3337
3338    #[test]
3339    fn test_exists_mysql_dialect() {
3340        // MySQL uses positional ? without numbers, so offset doesn't change the SQL
3341        let expr = Expr::exists(
3342            "SELECT 1 FROM orders WHERE status = ?",
3343            vec![Value::Text("active".to_string())],
3344        );
3345        let mut params = Vec::new();
3346        let sql = expr.build_with_dialect(Dialect::Mysql, &mut params, 0);
3347        assert_eq!(sql, "EXISTS (SELECT 1 FROM orders WHERE status = ?)");
3348        assert_eq!(params.len(), 1);
3349    }
3350
3351    #[test]
3352    fn test_not_exists_with_offset() {
3353        let expr = Expr::not_exists(
3354            "SELECT 1 FROM orders WHERE status = $1",
3355            vec![Value::Text("pending".to_string())],
3356        );
3357        let mut params = Vec::new();
3358        let sql = expr.build(&mut params, 5);
3359        assert_eq!(sql, "NOT EXISTS (SELECT 1 FROM orders WHERE status = $6)");
3360        assert_eq!(params.len(), 1);
3361    }
3362
3363    // ==================== Placeholder Adjustment Tests ====================
3364
3365    #[test]
3366    fn test_adjust_placeholder_indices_postgres() {
3367        let sql = "SELECT * FROM t WHERE a = $1 AND b = $2";
3368        let adjusted = super::adjust_placeholder_indices(sql, 3, Dialect::Postgres);
3369        assert_eq!(adjusted, "SELECT * FROM t WHERE a = $4 AND b = $5");
3370    }
3371
3372    #[test]
3373    fn test_adjust_placeholder_indices_sqlite() {
3374        let sql = "SELECT * FROM t WHERE a = ?1 AND b = ?2";
3375        let adjusted = super::adjust_placeholder_indices(sql, 3, Dialect::Sqlite);
3376        assert_eq!(adjusted, "SELECT * FROM t WHERE a = ?4 AND b = ?5");
3377    }
3378
3379    #[test]
3380    fn test_adjust_placeholder_indices_zero_offset() {
3381        let sql = "SELECT * FROM t WHERE a = $1";
3382        let adjusted = super::adjust_placeholder_indices(sql, 0, Dialect::Postgres);
3383        assert_eq!(adjusted, sql);
3384    }
3385
3386    #[test]
3387    fn test_adjust_placeholder_indices_mysql() {
3388        // MySQL uses ? without indices, so no adjustment
3389        let sql = "SELECT * FROM t WHERE a = ? AND b = ?";
3390        let adjusted = super::adjust_placeholder_indices(sql, 3, Dialect::Mysql);
3391        assert_eq!(adjusted, sql);
3392    }
3393
3394    // ==================== JSON Expression Tests ====================
3395
3396    #[test]
3397    fn test_json_get_key_postgres() {
3398        let expr = Expr::col("data").json_get("name");
3399        let mut params = Vec::new();
3400        let sql = expr.build_with_dialect(Dialect::Postgres, &mut params, 0);
3401        assert_eq!(sql, "\"data\" -> 'name'");
3402        assert!(params.is_empty());
3403    }
3404
3405    #[test]
3406    fn test_json_get_key_mysql() {
3407        let expr = Expr::col("data").json_get("name");
3408        let mut params = Vec::new();
3409        let sql = expr.build_with_dialect(Dialect::Mysql, &mut params, 0);
3410        assert_eq!(sql, "JSON_EXTRACT(`data`, '$.name')");
3411        assert!(params.is_empty());
3412    }
3413
3414    #[test]
3415    fn test_json_get_key_sqlite() {
3416        let expr = Expr::col("data").json_get("name");
3417        let mut params = Vec::new();
3418        let sql = expr.build_with_dialect(Dialect::Sqlite, &mut params, 0);
3419        assert_eq!(sql, "json_extract(\"data\", '$.name')");
3420        assert!(params.is_empty());
3421    }
3422
3423    #[test]
3424    fn test_json_get_index_postgres() {
3425        let expr = Expr::col("items").json_get_index(0);
3426        let mut params = Vec::new();
3427        let sql = expr.build_with_dialect(Dialect::Postgres, &mut params, 0);
3428        assert_eq!(sql, "\"items\" -> 0");
3429    }
3430
3431    #[test]
3432    fn test_json_get_index_mysql() {
3433        let expr = Expr::col("items").json_get_index(0);
3434        let mut params = Vec::new();
3435        let sql = expr.build_with_dialect(Dialect::Mysql, &mut params, 0);
3436        assert_eq!(sql, "JSON_EXTRACT(`items`, '$[0]')");
3437    }
3438
3439    #[test]
3440    fn test_json_get_text_postgres() {
3441        let expr = Expr::col("data").json_get_text("name");
3442        let mut params = Vec::new();
3443        let sql = expr.build_with_dialect(Dialect::Postgres, &mut params, 0);
3444        assert_eq!(sql, "\"data\" ->> 'name'");
3445    }
3446
3447    #[test]
3448    fn test_json_get_text_mysql() {
3449        let expr = Expr::col("data").json_get_text("name");
3450        let mut params = Vec::new();
3451        let sql = expr.build_with_dialect(Dialect::Mysql, &mut params, 0);
3452        assert_eq!(sql, "JSON_UNQUOTE(JSON_EXTRACT(`data`, '$.name'))");
3453    }
3454
3455    #[test]
3456    fn test_json_path_postgres() {
3457        let expr = Expr::col("data").json_path(&["address", "city"]);
3458        let mut params = Vec::new();
3459        let sql = expr.build_with_dialect(Dialect::Postgres, &mut params, 0);
3460        assert_eq!(sql, "\"data\" #> '{address, city}'");
3461    }
3462
3463    #[test]
3464    fn test_json_path_mysql() {
3465        let expr = Expr::col("data").json_path(&["address", "city"]);
3466        let mut params = Vec::new();
3467        let sql = expr.build_with_dialect(Dialect::Mysql, &mut params, 0);
3468        assert_eq!(sql, "JSON_EXTRACT(`data`, '$.address.city')");
3469    }
3470
3471    #[test]
3472    fn test_json_path_text_postgres() {
3473        let expr = Expr::col("data").json_path_text(&["address", "city"]);
3474        let mut params = Vec::new();
3475        let sql = expr.build_with_dialect(Dialect::Postgres, &mut params, 0);
3476        assert_eq!(sql, "\"data\" #>> '{address, city}'");
3477    }
3478
3479    #[test]
3480    fn test_json_contains_postgres() {
3481        let expr = Expr::col("tags").json_contains(Expr::raw("'[\"rust\"]'"));
3482        let mut params = Vec::new();
3483        let sql = expr.build_with_dialect(Dialect::Postgres, &mut params, 0);
3484        assert_eq!(sql, "\"tags\" @> '[\"rust\"]'");
3485    }
3486
3487    #[test]
3488    fn test_json_contains_mysql() {
3489        let expr = Expr::col("tags").json_contains(Expr::raw("'[\"rust\"]'"));
3490        let mut params = Vec::new();
3491        let sql = expr.build_with_dialect(Dialect::Mysql, &mut params, 0);
3492        assert_eq!(sql, "JSON_CONTAINS(`tags`, '[\"rust\"]')");
3493    }
3494
3495    #[test]
3496    fn test_json_has_key_postgres() {
3497        let expr = Expr::col("data").json_has_key("email");
3498        let mut params = Vec::new();
3499        let sql = expr.build_with_dialect(Dialect::Postgres, &mut params, 0);
3500        assert_eq!(sql, "\"data\" ? 'email'");
3501    }
3502
3503    #[test]
3504    fn test_json_has_key_mysql() {
3505        let expr = Expr::col("data").json_has_key("email");
3506        let mut params = Vec::new();
3507        let sql = expr.build_with_dialect(Dialect::Mysql, &mut params, 0);
3508        assert_eq!(sql, "JSON_CONTAINS_PATH(`data`, 'one', '$.email')");
3509    }
3510
3511    #[test]
3512    fn test_json_has_key_sqlite() {
3513        let expr = Expr::col("data").json_has_key("email");
3514        let mut params = Vec::new();
3515        let sql = expr.build_with_dialect(Dialect::Sqlite, &mut params, 0);
3516        assert_eq!(sql, "json_type(\"data\", '$.email') IS NOT NULL");
3517    }
3518
3519    #[test]
3520    fn test_json_has_any_key_postgres() {
3521        let expr = Expr::col("data").json_has_any_key(&["email", "phone"]);
3522        let mut params = Vec::new();
3523        let sql = expr.build_with_dialect(Dialect::Postgres, &mut params, 0);
3524        assert_eq!(sql, "\"data\" ?| array['email', 'phone']");
3525    }
3526
3527    #[test]
3528    fn test_json_has_all_keys_postgres() {
3529        let expr = Expr::col("data").json_has_all_keys(&["email", "phone"]);
3530        let mut params = Vec::new();
3531        let sql = expr.build_with_dialect(Dialect::Postgres, &mut params, 0);
3532        assert_eq!(sql, "\"data\" ?& array['email', 'phone']");
3533    }
3534
3535    #[test]
3536    fn test_json_array_length_postgres() {
3537        let expr = Expr::col("items").json_array_length();
3538        let mut params = Vec::new();
3539        let sql = expr.build_with_dialect(Dialect::Postgres, &mut params, 0);
3540        assert_eq!(sql, "jsonb_array_length(\"items\")");
3541    }
3542
3543    #[test]
3544    fn test_json_array_length_mysql() {
3545        let expr = Expr::col("items").json_array_length();
3546        let mut params = Vec::new();
3547        let sql = expr.build_with_dialect(Dialect::Mysql, &mut params, 0);
3548        assert_eq!(sql, "JSON_LENGTH(`items`)");
3549    }
3550
3551    #[test]
3552    fn test_json_array_length_sqlite() {
3553        let expr = Expr::col("items").json_array_length();
3554        let mut params = Vec::new();
3555        let sql = expr.build_with_dialect(Dialect::Sqlite, &mut params, 0);
3556        assert_eq!(sql, "json_array_length(\"items\")");
3557    }
3558
3559    #[test]
3560    fn test_json_typeof_postgres() {
3561        let expr = Expr::col("data").json_typeof();
3562        let mut params = Vec::new();
3563        let sql = expr.build_with_dialect(Dialect::Postgres, &mut params, 0);
3564        assert_eq!(sql, "jsonb_typeof(\"data\")");
3565    }
3566
3567    #[test]
3568    fn test_json_typeof_mysql() {
3569        let expr = Expr::col("data").json_typeof();
3570        let mut params = Vec::new();
3571        let sql = expr.build_with_dialect(Dialect::Mysql, &mut params, 0);
3572        assert_eq!(sql, "JSON_TYPE(`data`)");
3573    }
3574
3575    #[test]
3576    fn test_json_chained_extraction() {
3577        // Test chaining: data -> 'user' ->> 'name'
3578        let expr = Expr::col("data").json_get("user").json_get_text("name");
3579        let mut params = Vec::new();
3580        let sql = expr.build_with_dialect(Dialect::Postgres, &mut params, 0);
3581        // The inner expression is first evaluated, then text extraction
3582        // This tests nested JSON expressions
3583        assert_eq!(sql, "\"data\" -> 'user' ->> 'name'");
3584    }
3585
3586    #[test]
3587    fn test_json_in_where_clause() {
3588        // Test JSON expression in a comparison
3589        let expr = Expr::col("data").json_get_text("status").eq("active");
3590        let mut params = Vec::new();
3591        let sql = expr.build_with_dialect(Dialect::Postgres, &mut params, 0);
3592        assert_eq!(sql, "\"data\" ->> 'status' = $1");
3593        assert_eq!(params.len(), 1);
3594    }
3595
3596    // ==================== Array Operations ====================
3597
3598    #[test]
3599    fn test_array_contains() {
3600        let expr = Expr::col("tags").array_contains(Expr::col("other_tags"));
3601        let mut params = Vec::new();
3602        let sql = expr.build_with_dialect(Dialect::Postgres, &mut params, 0);
3603        assert_eq!(sql, "\"tags\" @> \"other_tags\"");
3604    }
3605
3606    #[test]
3607    fn test_array_contained_by() {
3608        let expr = Expr::col("tags").array_contained_by(Expr::col("all_tags"));
3609        let mut params = Vec::new();
3610        let sql = expr.build_with_dialect(Dialect::Postgres, &mut params, 0);
3611        assert_eq!(sql, "\"tags\" <@ \"all_tags\"");
3612    }
3613
3614    #[test]
3615    fn test_array_overlap() {
3616        let expr = Expr::col("tags").array_overlap(Expr::col("search_tags"));
3617        let mut params = Vec::new();
3618        let sql = expr.build_with_dialect(Dialect::Postgres, &mut params, 0);
3619        assert_eq!(sql, "\"tags\" && \"search_tags\"");
3620    }
3621
3622    #[test]
3623    fn test_array_any_eq() {
3624        let expr = Expr::col("tags").array_any_eq("admin");
3625        let mut params = Vec::new();
3626        let sql = expr.build_with_dialect(Dialect::Postgres, &mut params, 0);
3627        assert_eq!(sql, "$1 = ANY(\"tags\")");
3628        assert_eq!(params.len(), 1);
3629    }
3630}