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