Skip to main content

polyglot_sql/
builder.rs

1//! Fluent SQL Builder API
2//!
3//! Provides a programmatic way to construct SQL [`Expression`] trees without parsing raw SQL
4//! strings. The API mirrors Python sqlglot's builder functions (`select()`, `from_()`,
5//! `condition()`, etc.) and is the primary entry point for constructing queries
6//! programmatically in Rust.
7//!
8//! # Design
9//!
10//! The builder is organized around a few key concepts:
11//!
12//! - **Expression helpers** ([`col`], [`lit`], [`star`], [`null`], [`boolean`], [`func`],
13//!   [`cast`], [`alias`], [`sql_expr`], [`condition`]) create leaf-level [`Expr`] values.
14//! - **Query starters** ([`select`], [`from`], [`delete`], [`insert_into`], [`update`])
15//!   return fluent builder structs ([`SelectBuilder`], [`DeleteBuilder`], etc.).
16//! - **[`Expr`]** wraps an [`Expression`] and exposes operator methods (`.eq()`, `.gt()`,
17//!   `.and()`, `.like()`, etc.) so conditions can be built without manual AST construction.
18//! - **[`IntoExpr`]** and **[`IntoLiteral`]** allow ergonomic coercion of `&str`, `i64`,
19//!   `f64`, and other primitives wherever an expression or literal is expected.
20//!
21//! # Examples
22//!
23//! ```
24//! use polyglot_sql::builder::*;
25//!
26//! // SELECT id, name FROM users WHERE age > 18 ORDER BY name LIMIT 10
27//! let expr = select(["id", "name"])
28//!     .from("users")
29//!     .where_(col("age").gt(lit(18)))
30//!     .order_by(["name"])
31//!     .limit(10)
32//!     .build();
33//! ```
34//!
35//! ```
36//! use polyglot_sql::builder::*;
37//!
38//! // CASE WHEN x > 0 THEN 'positive' ELSE 'non-positive' END
39//! let expr = case()
40//!     .when(col("x").gt(lit(0)), lit("positive"))
41//!     .else_(lit("non-positive"))
42//!     .build();
43//! ```
44//!
45//! ```
46//! use polyglot_sql::builder::*;
47//!
48//! // SELECT id FROM a UNION ALL SELECT id FROM b ORDER BY id LIMIT 5
49//! let expr = union_all(
50//!     select(["id"]).from("a"),
51//!     select(["id"]).from("b"),
52//! )
53//! .order_by(["id"])
54//! .limit(5)
55//! .build();
56//! ```
57
58use crate::expressions::*;
59use crate::generator::Generator;
60use crate::parser::Parser;
61
62fn is_safe_identifier_name(name: &str) -> bool {
63    if name.is_empty() {
64        return false;
65    }
66
67    let mut chars = name.chars();
68    let Some(first) = chars.next() else {
69        return false;
70    };
71
72    if !(first == '_' || first.is_ascii_alphabetic()) {
73        return false;
74    }
75
76    chars.all(|c| c == '_' || c.is_ascii_alphanumeric())
77}
78
79fn builder_identifier(name: &str) -> Identifier {
80    if name == "*" || is_safe_identifier_name(name) {
81        Identifier::new(name)
82    } else {
83        Identifier::quoted(name)
84    }
85}
86
87fn builder_table_ref(name: &str) -> TableRef {
88    let parts: Vec<&str> = name.split('.').collect();
89
90    match parts.len() {
91        3 => {
92            let mut t = TableRef::new(parts[2]);
93            t.name = builder_identifier(parts[2]);
94            t.schema = Some(builder_identifier(parts[1]));
95            t.catalog = Some(builder_identifier(parts[0]));
96            t
97        }
98        2 => {
99            let mut t = TableRef::new(parts[1]);
100            t.name = builder_identifier(parts[1]);
101            t.schema = Some(builder_identifier(parts[0]));
102            t
103        }
104        _ => {
105            let first = parts.first().copied().unwrap_or("");
106            let mut t = TableRef::new(first);
107            t.name = builder_identifier(first);
108            t
109        }
110    }
111}
112
113// ---------------------------------------------------------------------------
114// Expression helpers
115// ---------------------------------------------------------------------------
116
117/// Create a column reference expression.
118///
119/// If `name` contains a dot, it is split on the **last** `.` to produce a table-qualified
120/// column (e.g. `"u.id"` becomes `u.id`). Unqualified names produce a bare column
121/// reference.
122///
123/// # Examples
124///
125/// ```
126/// use polyglot_sql::builder::col;
127///
128/// // Unqualified column
129/// let c = col("name");
130/// assert_eq!(c.to_sql(), "name");
131///
132/// // Table-qualified column
133/// let c = col("users.name");
134/// assert_eq!(c.to_sql(), "users.name");
135/// ```
136pub fn col(name: &str) -> Expr {
137    if let Some((table, column)) = name.rsplit_once('.') {
138        Expr(Expression::boxed_column(Column {
139            name: builder_identifier(column),
140            table: Some(builder_identifier(table)),
141            join_mark: false,
142            trailing_comments: Vec::new(),
143            span: None,
144            inferred_type: None,
145        }))
146    } else {
147        Expr(Expression::boxed_column(Column {
148            name: builder_identifier(name),
149            table: None,
150            join_mark: false,
151            trailing_comments: Vec::new(),
152            span: None,
153            inferred_type: None,
154        }))
155    }
156}
157
158/// Create a literal expression from any type implementing [`IntoLiteral`].
159///
160/// Supported types include `&str` / `String` (string literal), `i32` / `i64` / `usize` /
161/// `f64` (numeric literal), and `bool` (boolean literal).
162///
163/// # Examples
164///
165/// ```
166/// use polyglot_sql::builder::lit;
167///
168/// let s = lit("hello");   // 'hello'
169/// let n = lit(42);        // 42
170/// let f = lit(3.14);      // 3.14
171/// let b = lit(true);      // TRUE
172/// ```
173pub fn lit<V: IntoLiteral>(value: V) -> Expr {
174    value.into_literal()
175}
176
177/// Create a star (`*`) expression, typically used in `SELECT *`.
178pub fn star() -> Expr {
179    Expr(Expression::star())
180}
181
182/// Create a SQL `NULL` literal expression.
183pub fn null() -> Expr {
184    Expr(Expression::Null(Null))
185}
186
187/// Create a SQL boolean literal expression (`TRUE` or `FALSE`).
188pub fn boolean(value: bool) -> Expr {
189    Expr(Expression::Boolean(BooleanLiteral { value }))
190}
191
192/// Create a table reference expression.
193///
194/// The `name` string is split on `.` to determine qualification level:
195///
196/// - `"table"` -- unqualified table reference
197/// - `"schema.table"` -- schema-qualified
198/// - `"catalog.schema.table"` -- fully qualified with catalog
199///
200/// # Examples
201///
202/// ```
203/// use polyglot_sql::builder::table;
204///
205/// let t = table("my_schema.users");
206/// assert_eq!(t.to_sql(), "my_schema.users");
207/// ```
208pub fn table(name: &str) -> Expr {
209    Expr(Expression::Table(Box::new(builder_table_ref(name))))
210}
211
212/// Create a SQL function call expression.
213///
214/// `name` is the function name (e.g. `"COUNT"`, `"UPPER"`, `"COALESCE"`), and `args`
215/// provides zero or more argument expressions.
216///
217/// # Examples
218///
219/// ```
220/// use polyglot_sql::builder::{func, col, star};
221///
222/// let upper = func("UPPER", [col("name")]);
223/// assert_eq!(upper.to_sql(), "UPPER(name)");
224///
225/// let count = func("COUNT", [star()]);
226/// assert_eq!(count.to_sql(), "COUNT(*)");
227/// ```
228pub fn func(name: &str, args: impl IntoIterator<Item = Expr>) -> Expr {
229    Expr(Expression::Function(Box::new(Function {
230        name: name.to_string(),
231        args: args.into_iter().map(|a| a.0).collect(),
232        ..Function::default()
233    })))
234}
235
236/// Create a `CAST(expr AS type)` expression.
237///
238/// The `to` parameter is parsed as a data type name. Common built-in types (`INT`, `BIGINT`,
239/// `VARCHAR`, `BOOLEAN`, `TIMESTAMP`, etc.) are recognized directly. More complex types
240/// (e.g. `"DECIMAL(10,2)"`, `"ARRAY<INT>"`) are parsed via the full SQL parser as a
241/// fallback.
242///
243/// # Examples
244///
245/// ```
246/// use polyglot_sql::builder::{cast, col};
247///
248/// let expr = cast(col("id"), "VARCHAR");
249/// assert_eq!(expr.to_sql(), "CAST(id AS VARCHAR)");
250/// ```
251pub fn cast(expr: Expr, to: &str) -> Expr {
252    let data_type = parse_simple_data_type(to);
253    Expr(Expression::Cast(Box::new(Cast {
254        this: expr.0,
255        to: data_type,
256        trailing_comments: Vec::new(),
257        double_colon_syntax: false,
258        format: None,
259        default: None,
260        inferred_type: None,
261    })))
262}
263
264/// Create a `NOT expr` unary expression.
265///
266/// Wraps the given expression in a logical negation. Equivalent to calling
267/// [`Expr::not()`] on the expression.
268pub fn not(expr: Expr) -> Expr {
269    Expr(Expression::Not(Box::new(UnaryOp::new(expr.0))))
270}
271
272/// Combine two expressions with `AND`.
273///
274/// Equivalent to `left.and(right)`. Useful when you do not have the left-hand side
275/// as the receiver.
276pub fn and(left: Expr, right: Expr) -> Expr {
277    left.and(right)
278}
279
280/// Combine two expressions with `OR`.
281///
282/// Equivalent to `left.or(right)`. Useful when you do not have the left-hand side
283/// as the receiver.
284pub fn or(left: Expr, right: Expr) -> Expr {
285    left.or(right)
286}
287
288/// Create an `expr AS name` alias expression.
289///
290/// This is the free-function form. The method form [`Expr::alias()`] is often more
291/// convenient for chaining.
292pub fn alias(expr: Expr, name: &str) -> Expr {
293    Expr(Expression::Alias(Box::new(Alias {
294        this: expr.0,
295        alias: builder_identifier(name),
296        column_aliases: Vec::new(),
297        pre_alias_comments: Vec::new(),
298        trailing_comments: Vec::new(),
299        inferred_type: None,
300    })))
301}
302
303/// Parse a raw SQL expression fragment into an [`Expr`].
304///
305/// Internally wraps the string in `SELECT <sql>`, parses it with the full SQL parser,
306/// and extracts the first expression from the SELECT list. This is useful for
307/// embedding complex SQL fragments (window functions, subquery predicates, etc.)
308/// that would be cumbersome to build purely through the builder API.
309///
310/// # Examples
311///
312/// ```
313/// use polyglot_sql::builder::sql_expr;
314///
315/// let expr = sql_expr("COALESCE(a, b, 0)");
316/// assert_eq!(expr.to_sql(), "COALESCE(a, b, 0)");
317///
318/// let cond = sql_expr("age > 18 AND status = 'active'");
319/// ```
320///
321/// # Panics
322///
323/// Panics if the SQL fragment cannot be parsed, or if the parser fails to extract a
324/// valid expression from the result. Invalid SQL will cause a panic with a message
325/// prefixed by `"sql_expr:"`.
326pub fn sql_expr(sql: &str) -> Expr {
327    let wrapped = format!("SELECT {}", sql);
328    let ast = Parser::parse_sql(&wrapped).expect("sql_expr: failed to parse SQL expression");
329    if let Expression::Select(s) = &ast[0] {
330        if let Some(first) = s.expressions.first() {
331            return Expr(first.clone());
332        }
333    }
334    panic!("sql_expr: failed to extract expression from parsed SQL");
335}
336
337/// Parse a SQL condition string into an [`Expr`].
338///
339/// This is a convenience alias for [`sql_expr()`]. The name `condition` reads more
340/// naturally when the fragment is intended as a WHERE or HAVING predicate.
341///
342/// # Panics
343///
344/// Panics under the same conditions as [`sql_expr()`].
345pub fn condition(sql: &str) -> Expr {
346    sql_expr(sql)
347}
348
349// ---------------------------------------------------------------------------
350// Function helpers — typed AST constructors
351// ---------------------------------------------------------------------------
352
353// -- Aggregates ---------------------------------------------------------------
354
355/// Create a `COUNT(expr)` expression.
356pub fn count(expr: Expr) -> Expr {
357    Expr(Expression::Count(Box::new(CountFunc {
358        this: Some(expr.0),
359        star: false,
360        distinct: false,
361        filter: None,
362        ignore_nulls: None,
363        original_name: None,
364        inferred_type: None,
365    })))
366}
367
368/// Create a `COUNT(*)` expression.
369pub fn count_star() -> Expr {
370    Expr(Expression::Count(Box::new(CountFunc {
371        this: None,
372        star: true,
373        distinct: false,
374        filter: None,
375        ignore_nulls: None,
376        original_name: None,
377        inferred_type: None,
378    })))
379}
380
381/// Create a `COUNT(DISTINCT expr)` expression.
382pub fn count_distinct(expr: Expr) -> Expr {
383    Expr(Expression::Count(Box::new(CountFunc {
384        this: Some(expr.0),
385        star: false,
386        distinct: true,
387        filter: None,
388        ignore_nulls: None,
389        original_name: None,
390        inferred_type: None,
391    })))
392}
393
394/// Create a `SUM(expr)` expression.
395pub fn sum(expr: Expr) -> Expr {
396    Expr(Expression::Sum(Box::new(AggFunc {
397        this: expr.0,
398        distinct: false,
399        filter: None,
400        order_by: vec![],
401        name: None,
402        ignore_nulls: None,
403        having_max: None,
404        limit: None,
405        inferred_type: None,
406    })))
407}
408
409/// Create an `AVG(expr)` expression.
410pub fn avg(expr: Expr) -> Expr {
411    Expr(Expression::Avg(Box::new(AggFunc {
412        this: expr.0,
413        distinct: false,
414        filter: None,
415        order_by: vec![],
416        name: None,
417        ignore_nulls: None,
418        having_max: None,
419        limit: None,
420        inferred_type: None,
421    })))
422}
423
424/// Create a `MIN(expr)` expression. Named `min_` to avoid conflict with `std::cmp::min`.
425pub fn min_(expr: Expr) -> Expr {
426    Expr(Expression::Min(Box::new(AggFunc {
427        this: expr.0,
428        distinct: false,
429        filter: None,
430        order_by: vec![],
431        name: None,
432        ignore_nulls: None,
433        having_max: None,
434        limit: None,
435        inferred_type: None,
436    })))
437}
438
439/// Create a `MAX(expr)` expression. Named `max_` to avoid conflict with `std::cmp::max`.
440pub fn max_(expr: Expr) -> Expr {
441    Expr(Expression::Max(Box::new(AggFunc {
442        this: expr.0,
443        distinct: false,
444        filter: None,
445        order_by: vec![],
446        name: None,
447        ignore_nulls: None,
448        having_max: None,
449        limit: None,
450        inferred_type: None,
451    })))
452}
453
454/// Create an `APPROX_DISTINCT(expr)` expression.
455pub fn approx_distinct(expr: Expr) -> Expr {
456    Expr(Expression::ApproxDistinct(Box::new(AggFunc {
457        this: expr.0,
458        distinct: false,
459        filter: None,
460        order_by: vec![],
461        name: None,
462        ignore_nulls: None,
463        having_max: None,
464        limit: None,
465        inferred_type: None,
466    })))
467}
468
469// -- String functions ---------------------------------------------------------
470
471/// Create an `UPPER(expr)` expression.
472pub fn upper(expr: Expr) -> Expr {
473    Expr(Expression::Upper(Box::new(UnaryFunc::new(expr.0))))
474}
475
476/// Create a `LOWER(expr)` expression.
477pub fn lower(expr: Expr) -> Expr {
478    Expr(Expression::Lower(Box::new(UnaryFunc::new(expr.0))))
479}
480
481/// Create a `LENGTH(expr)` expression.
482pub fn length(expr: Expr) -> Expr {
483    Expr(Expression::Length(Box::new(UnaryFunc::new(expr.0))))
484}
485
486/// Create a `TRIM(expr)` expression.
487pub fn trim(expr: Expr) -> Expr {
488    Expr(Expression::Trim(Box::new(TrimFunc {
489        this: expr.0,
490        characters: None,
491        position: TrimPosition::Both,
492        sql_standard_syntax: false,
493        position_explicit: false,
494    })))
495}
496
497/// Create an `LTRIM(expr)` expression.
498pub fn ltrim(expr: Expr) -> Expr {
499    Expr(Expression::LTrim(Box::new(UnaryFunc::new(expr.0))))
500}
501
502/// Create an `RTRIM(expr)` expression.
503pub fn rtrim(expr: Expr) -> Expr {
504    Expr(Expression::RTrim(Box::new(UnaryFunc::new(expr.0))))
505}
506
507/// Create a `REVERSE(expr)` expression.
508pub fn reverse(expr: Expr) -> Expr {
509    Expr(Expression::Reverse(Box::new(UnaryFunc::new(expr.0))))
510}
511
512/// Create an `INITCAP(expr)` expression.
513pub fn initcap(expr: Expr) -> Expr {
514    Expr(Expression::Initcap(Box::new(UnaryFunc::new(expr.0))))
515}
516
517/// Create a `SUBSTRING(expr, start, len)` expression.
518pub fn substring(expr: Expr, start: Expr, len: Option<Expr>) -> Expr {
519    Expr(Expression::Substring(Box::new(SubstringFunc {
520        this: expr.0,
521        start: start.0,
522        length: len.map(|l| l.0),
523        from_for_syntax: false,
524    })))
525}
526
527/// Create a `REPLACE(expr, old, new)` expression. Named `replace_` to avoid
528/// conflict with the `str::replace` method.
529pub fn replace_(expr: Expr, old: Expr, new: Expr) -> Expr {
530    Expr(Expression::Replace(Box::new(ReplaceFunc {
531        this: expr.0,
532        old: old.0,
533        new: new.0,
534    })))
535}
536
537/// Create a `CONCAT_WS(separator, exprs...)` expression.
538pub fn concat_ws(separator: Expr, exprs: impl IntoIterator<Item = Expr>) -> Expr {
539    Expr(Expression::ConcatWs(Box::new(ConcatWs {
540        separator: separator.0,
541        expressions: exprs.into_iter().map(|e| e.0).collect(),
542    })))
543}
544
545// -- Null handling ------------------------------------------------------------
546
547/// Create a `COALESCE(exprs...)` expression.
548pub fn coalesce(exprs: impl IntoIterator<Item = Expr>) -> Expr {
549    Expr(Expression::Coalesce(Box::new(VarArgFunc {
550        expressions: exprs.into_iter().map(|e| e.0).collect(),
551        original_name: None,
552        inferred_type: None,
553    })))
554}
555
556/// Create a `NULLIF(expr1, expr2)` expression.
557pub fn null_if(expr1: Expr, expr2: Expr) -> Expr {
558    Expr(Expression::NullIf(Box::new(BinaryFunc {
559        this: expr1.0,
560        expression: expr2.0,
561        original_name: None,
562        inferred_type: None,
563    })))
564}
565
566/// Create an `IFNULL(expr, fallback)` expression.
567pub fn if_null(expr: Expr, fallback: Expr) -> Expr {
568    Expr(Expression::IfNull(Box::new(BinaryFunc {
569        this: expr.0,
570        expression: fallback.0,
571        original_name: None,
572        inferred_type: None,
573    })))
574}
575
576// -- Math functions -----------------------------------------------------------
577
578/// Create an `ABS(expr)` expression.
579pub fn abs(expr: Expr) -> Expr {
580    Expr(Expression::Abs(Box::new(UnaryFunc::new(expr.0))))
581}
582
583/// Create a `ROUND(expr, decimals)` expression.
584pub fn round(expr: Expr, decimals: Option<Expr>) -> Expr {
585    Expr(Expression::Round(Box::new(RoundFunc {
586        this: expr.0,
587        decimals: decimals.map(|d| d.0),
588    })))
589}
590
591/// Create a `FLOOR(expr)` expression.
592pub fn floor(expr: Expr) -> Expr {
593    Expr(Expression::Floor(Box::new(FloorFunc {
594        this: expr.0,
595        scale: None,
596        to: None,
597    })))
598}
599
600/// Create a `CEIL(expr)` expression.
601pub fn ceil(expr: Expr) -> Expr {
602    Expr(Expression::Ceil(Box::new(CeilFunc {
603        this: expr.0,
604        decimals: None,
605        to: None,
606    })))
607}
608
609/// Create a `POWER(base, exp)` expression.
610pub fn power(base: Expr, exponent: Expr) -> Expr {
611    Expr(Expression::Power(Box::new(BinaryFunc {
612        this: base.0,
613        expression: exponent.0,
614        original_name: None,
615        inferred_type: None,
616    })))
617}
618
619/// Create a `SQRT(expr)` expression.
620pub fn sqrt(expr: Expr) -> Expr {
621    Expr(Expression::Sqrt(Box::new(UnaryFunc::new(expr.0))))
622}
623
624/// Create a `LN(expr)` expression.
625pub fn ln(expr: Expr) -> Expr {
626    Expr(Expression::Ln(Box::new(UnaryFunc::new(expr.0))))
627}
628
629/// Create an `EXP(expr)` expression. Named `exp_` to avoid conflict with `std::f64::consts`.
630pub fn exp_(expr: Expr) -> Expr {
631    Expr(Expression::Exp(Box::new(UnaryFunc::new(expr.0))))
632}
633
634/// Create a `SIGN(expr)` expression.
635pub fn sign(expr: Expr) -> Expr {
636    Expr(Expression::Sign(Box::new(UnaryFunc::new(expr.0))))
637}
638
639/// Create a `GREATEST(exprs...)` expression.
640pub fn greatest(exprs: impl IntoIterator<Item = Expr>) -> Expr {
641    Expr(Expression::Greatest(Box::new(VarArgFunc {
642        expressions: exprs.into_iter().map(|e| e.0).collect(),
643        original_name: None,
644        inferred_type: None,
645    })))
646}
647
648/// Create a `LEAST(exprs...)` expression.
649pub fn least(exprs: impl IntoIterator<Item = Expr>) -> Expr {
650    Expr(Expression::Least(Box::new(VarArgFunc {
651        expressions: exprs.into_iter().map(|e| e.0).collect(),
652        original_name: None,
653        inferred_type: None,
654    })))
655}
656
657// -- Date/time functions ------------------------------------------------------
658
659/// Create a `CURRENT_DATE` expression.
660pub fn current_date_() -> Expr {
661    Expr(Expression::CurrentDate(CurrentDate))
662}
663
664/// Create a `CURRENT_TIME` expression.
665pub fn current_time_() -> Expr {
666    Expr(Expression::CurrentTime(CurrentTime { precision: None }))
667}
668
669/// Create a `CURRENT_TIMESTAMP` expression.
670pub fn current_timestamp_() -> Expr {
671    Expr(Expression::CurrentTimestamp(CurrentTimestamp {
672        precision: None,
673        sysdate: false,
674    }))
675}
676
677/// Create an `EXTRACT(field FROM expr)` expression.
678pub fn extract_(field: &str, expr: Expr) -> Expr {
679    Expr(Expression::Extract(Box::new(ExtractFunc {
680        this: expr.0,
681        field: parse_datetime_field(field),
682    })))
683}
684
685/// Parse a datetime field name string into a [`DateTimeField`] enum value.
686fn parse_datetime_field(field: &str) -> DateTimeField {
687    match field.to_uppercase().as_str() {
688        "YEAR" => DateTimeField::Year,
689        "MONTH" => DateTimeField::Month,
690        "DAY" => DateTimeField::Day,
691        "HOUR" => DateTimeField::Hour,
692        "MINUTE" => DateTimeField::Minute,
693        "SECOND" => DateTimeField::Second,
694        "MILLISECOND" => DateTimeField::Millisecond,
695        "MICROSECOND" => DateTimeField::Microsecond,
696        "DOW" | "DAYOFWEEK" => DateTimeField::DayOfWeek,
697        "DOY" | "DAYOFYEAR" => DateTimeField::DayOfYear,
698        "WEEK" => DateTimeField::Week,
699        "QUARTER" => DateTimeField::Quarter,
700        "EPOCH" => DateTimeField::Epoch,
701        "TIMEZONE" => DateTimeField::Timezone,
702        "TIMEZONE_HOUR" => DateTimeField::TimezoneHour,
703        "TIMEZONE_MINUTE" => DateTimeField::TimezoneMinute,
704        "DATE" => DateTimeField::Date,
705        "TIME" => DateTimeField::Time,
706        other => DateTimeField::Custom(other.to_string()),
707    }
708}
709
710// -- Window functions ---------------------------------------------------------
711
712/// Create a `ROW_NUMBER()` expression.
713pub fn row_number() -> Expr {
714    Expr(Expression::RowNumber(RowNumber))
715}
716
717/// Create a `RANK()` expression. Named `rank_` to avoid confusion with `Rank` struct.
718pub fn rank_() -> Expr {
719    Expr(Expression::Rank(Rank {
720        order_by: None,
721        args: vec![],
722    }))
723}
724
725/// Create a `DENSE_RANK()` expression.
726pub fn dense_rank() -> Expr {
727    Expr(Expression::DenseRank(DenseRank { args: vec![] }))
728}
729
730// ---------------------------------------------------------------------------
731// Query starters
732// ---------------------------------------------------------------------------
733
734/// Start building a SELECT query with the given column expressions.
735///
736/// Accepts any iterable of items implementing [`IntoExpr`], which includes `&str`
737/// (interpreted as column names), [`Expr`] values, and raw [`Expression`] nodes.
738/// Returns a [`SelectBuilder`] that can be further refined with `.from()`, `.where_()`,
739/// `.order_by()`, etc.
740///
741/// # Examples
742///
743/// ```
744/// use polyglot_sql::builder::*;
745///
746/// // Using string slices (converted to column refs automatically)
747/// let sql = select(["id", "name"]).from("users").to_sql();
748/// assert_eq!(sql, "SELECT id, name FROM users");
749///
750/// // Using Expr values for computed columns
751/// let sql = select([col("price").mul(col("qty")).alias("total")])
752///     .from("items")
753///     .to_sql();
754/// assert_eq!(sql, "SELECT price * qty AS total FROM items");
755/// ```
756pub fn select<I, E>(expressions: I) -> SelectBuilder
757where
758    I: IntoIterator<Item = E>,
759    E: IntoExpr,
760{
761    let mut builder = SelectBuilder::new();
762    for expr in expressions {
763        builder.select = builder.select.column(expr.into_expr().0);
764    }
765    builder
766}
767
768/// Start building a SELECT query beginning with a FROM clause.
769///
770/// Returns a [`SelectBuilder`] with the FROM clause already set. Use
771/// [`SelectBuilder::select_cols()`] to add columns afterward. This is an alternative
772/// entry point for queries where specifying the table first feels more natural.
773///
774/// # Examples
775///
776/// ```
777/// use polyglot_sql::builder::*;
778///
779/// let sql = from("users").select_cols(["id", "name"]).to_sql();
780/// assert_eq!(sql, "SELECT id, name FROM users");
781/// ```
782pub fn from(table_name: &str) -> SelectBuilder {
783    let mut builder = SelectBuilder::new();
784    builder.select.from = Some(From {
785        expressions: vec![Expression::Table(Box::new(builder_table_ref(table_name)))],
786    });
787    builder
788}
789
790/// Start building a `DELETE FROM` statement targeting the given table.
791///
792/// Returns a [`DeleteBuilder`] which supports `.where_()` to add a predicate.
793///
794/// # Examples
795///
796/// ```
797/// use polyglot_sql::builder::*;
798///
799/// let sql = delete("users").where_(col("id").eq(lit(1))).to_sql();
800/// assert_eq!(sql, "DELETE FROM users WHERE id = 1");
801/// ```
802pub fn delete(table_name: &str) -> DeleteBuilder {
803    DeleteBuilder {
804        delete: Delete {
805            table: builder_table_ref(table_name),
806            hint: None,
807            on_cluster: None,
808            alias: None,
809            alias_explicit_as: false,
810            using: Vec::new(),
811            where_clause: None,
812            output: None,
813            leading_comments: Vec::new(),
814            with: None,
815            limit: None,
816            order_by: None,
817            returning: Vec::new(),
818            tables: Vec::new(),
819            tables_from_using: false,
820            joins: Vec::new(),
821            force_index: None,
822            no_from: false,
823        },
824    }
825}
826
827/// Start building an `INSERT INTO` statement targeting the given table.
828///
829/// Returns an [`InsertBuilder`] which supports `.columns()`, `.values()`, and
830/// `.query()` for INSERT ... SELECT.
831///
832/// # Examples
833///
834/// ```
835/// use polyglot_sql::builder::*;
836///
837/// let sql = insert_into("users")
838///     .columns(["id", "name"])
839///     .values([lit(1), lit("Alice")])
840///     .to_sql();
841/// assert_eq!(sql, "INSERT INTO users (id, name) VALUES (1, 'Alice')");
842/// ```
843pub fn insert_into(table_name: &str) -> InsertBuilder {
844    InsertBuilder {
845        insert: Insert {
846            table: builder_table_ref(table_name),
847            columns: Vec::new(),
848            values: Vec::new(),
849            query: None,
850            overwrite: false,
851            partition: Vec::new(),
852            directory: None,
853            returning: Vec::new(),
854            output: None,
855            on_conflict: None,
856            leading_comments: Vec::new(),
857            if_exists: false,
858            with: None,
859            ignore: false,
860            source_alias: None,
861            alias: None,
862            alias_explicit_as: false,
863            default_values: false,
864            by_name: false,
865            conflict_action: None,
866            is_replace: false,
867            hint: None,
868            replace_where: None,
869            source: None,
870            function_target: None,
871            partition_by: None,
872            settings: Vec::new(),
873        },
874    }
875}
876
877/// Start building an `UPDATE` statement targeting the given table.
878///
879/// Returns an [`UpdateBuilder`] which supports `.set()` for column assignments,
880/// `.where_()` for predicates, and `.from()` for PostgreSQL/Snowflake-style
881/// UPDATE ... FROM syntax.
882///
883/// # Examples
884///
885/// ```
886/// use polyglot_sql::builder::*;
887///
888/// let sql = update("users")
889///     .set("name", lit("Bob"))
890///     .where_(col("id").eq(lit(1)))
891///     .to_sql();
892/// assert_eq!(sql, "UPDATE users SET name = 'Bob' WHERE id = 1");
893/// ```
894pub fn update(table_name: &str) -> UpdateBuilder {
895    UpdateBuilder {
896        update: Update {
897            table: builder_table_ref(table_name),
898            hint: None,
899            extra_tables: Vec::new(),
900            table_joins: Vec::new(),
901            set: Vec::new(),
902            from_clause: None,
903            from_joins: Vec::new(),
904            where_clause: None,
905            returning: Vec::new(),
906            output: None,
907            with: None,
908            leading_comments: Vec::new(),
909            limit: None,
910            order_by: None,
911            from_before_set: false,
912        },
913    }
914}
915
916// ---------------------------------------------------------------------------
917// Expr wrapper (for operator methods)
918// ---------------------------------------------------------------------------
919
920/// A thin wrapper around [`Expression`] that provides fluent operator methods.
921///
922/// `Expr` is the primary value type flowing through the builder API. It wraps a single
923/// AST [`Expression`] node and adds convenience methods for comparisons (`.eq()`,
924/// `.gt()`, etc.), logical connectives (`.and()`, `.or()`, `.not()`), arithmetic
925/// (`.add()`, `.sub()`, `.mul()`, `.div()`), pattern matching (`.like()`, `.ilike()`,
926/// `.rlike()`), and other SQL operations (`.in_list()`, `.between()`, `.is_null()`,
927/// `.alias()`, `.cast()`, `.asc()`, `.desc()`).
928///
929/// The inner [`Expression`] is publicly accessible via the `.0` field or
930/// [`Expr::into_inner()`].
931///
932/// # Examples
933///
934/// ```
935/// use polyglot_sql::builder::*;
936///
937/// let condition = col("age").gte(lit(18)).and(col("active").eq(boolean(true)));
938/// assert_eq!(condition.to_sql(), "age >= 18 AND active = TRUE");
939/// ```
940#[derive(Debug, Clone)]
941pub struct Expr(pub Expression);
942
943impl Expr {
944    /// Consume this wrapper and return the inner [`Expression`] node.
945    pub fn into_inner(self) -> Expression {
946        self.0
947    }
948
949    /// Generate a SQL string from this expression using the default (generic) dialect.
950    ///
951    /// Returns an empty string if generation fails.
952    pub fn to_sql(&self) -> String {
953        Generator::sql(&self.0).unwrap_or_default()
954    }
955
956    // -- Comparison operators --
957
958    /// Produce a `self = other` equality comparison.
959    pub fn eq(self, other: Expr) -> Expr {
960        Expr(Expression::Eq(Box::new(binary_op(self.0, other.0))))
961    }
962
963    /// Produce a `self <> other` inequality comparison.
964    pub fn neq(self, other: Expr) -> Expr {
965        Expr(Expression::Neq(Box::new(binary_op(self.0, other.0))))
966    }
967
968    /// Produce a `self < other` less-than comparison.
969    pub fn lt(self, other: Expr) -> Expr {
970        Expr(Expression::Lt(Box::new(binary_op(self.0, other.0))))
971    }
972
973    /// Produce a `self <= other` less-than-or-equal comparison.
974    pub fn lte(self, other: Expr) -> Expr {
975        Expr(Expression::Lte(Box::new(binary_op(self.0, other.0))))
976    }
977
978    /// Produce a `self > other` greater-than comparison.
979    pub fn gt(self, other: Expr) -> Expr {
980        Expr(Expression::Gt(Box::new(binary_op(self.0, other.0))))
981    }
982
983    /// Produce a `self >= other` greater-than-or-equal comparison.
984    pub fn gte(self, other: Expr) -> Expr {
985        Expr(Expression::Gte(Box::new(binary_op(self.0, other.0))))
986    }
987
988    // -- Logical operators --
989
990    /// Produce a `self AND other` logical conjunction.
991    pub fn and(self, other: Expr) -> Expr {
992        Expr(Expression::And(Box::new(binary_op(self.0, other.0))))
993    }
994
995    /// Produce a `self OR other` logical disjunction.
996    pub fn or(self, other: Expr) -> Expr {
997        Expr(Expression::Or(Box::new(binary_op(self.0, other.0))))
998    }
999
1000    /// Produce a `NOT self` logical negation.
1001    pub fn not(self) -> Expr {
1002        Expr(Expression::Not(Box::new(UnaryOp::new(self.0))))
1003    }
1004
1005    /// Produce a `self XOR other` logical exclusive-or.
1006    pub fn xor(self, other: Expr) -> Expr {
1007        Expr(Expression::Xor(Box::new(Xor {
1008            this: Some(Box::new(self.0)),
1009            expression: Some(Box::new(other.0)),
1010            expressions: vec![],
1011        })))
1012    }
1013
1014    // -- Arithmetic operators --
1015
1016    /// Produce a `self + other` addition expression.
1017    pub fn add(self, other: Expr) -> Expr {
1018        Expr(Expression::Add(Box::new(binary_op(self.0, other.0))))
1019    }
1020
1021    /// Produce a `self - other` subtraction expression.
1022    pub fn sub(self, other: Expr) -> Expr {
1023        Expr(Expression::Sub(Box::new(binary_op(self.0, other.0))))
1024    }
1025
1026    /// Produce a `self * other` multiplication expression.
1027    pub fn mul(self, other: Expr) -> Expr {
1028        Expr(Expression::Mul(Box::new(binary_op(self.0, other.0))))
1029    }
1030
1031    /// Produce a `self / other` division expression.
1032    pub fn div(self, other: Expr) -> Expr {
1033        Expr(Expression::Div(Box::new(binary_op(self.0, other.0))))
1034    }
1035
1036    // -- Other operators --
1037
1038    /// Produce a `self IS NULL` predicate.
1039    pub fn is_null(self) -> Expr {
1040        Expr(Expression::Is(Box::new(BinaryOp {
1041            left: self.0,
1042            right: Expression::Null(Null),
1043            left_comments: Vec::new(),
1044            operator_comments: Vec::new(),
1045            trailing_comments: Vec::new(),
1046            inferred_type: None,
1047        })))
1048    }
1049
1050    /// Produce a `self IS NOT NULL` predicate (implemented as `NOT (self IS NULL)`).
1051    pub fn is_not_null(self) -> Expr {
1052        Expr(Expression::Not(Box::new(UnaryOp::new(Expression::Is(
1053            Box::new(BinaryOp {
1054                left: self.0,
1055                right: Expression::Null(Null),
1056                left_comments: Vec::new(),
1057                operator_comments: Vec::new(),
1058                trailing_comments: Vec::new(),
1059                inferred_type: None,
1060            }),
1061        )))))
1062    }
1063
1064    /// Produce a `self IN (values...)` membership test.
1065    ///
1066    /// Each element of `values` becomes an item in the parenthesized list.
1067    pub fn in_list(self, values: impl IntoIterator<Item = Expr>) -> Expr {
1068        Expr(Expression::In(Box::new(In {
1069            this: self.0,
1070            expressions: values.into_iter().map(|v| v.0).collect(),
1071            query: None,
1072            not: false,
1073            global: false,
1074            unnest: None,
1075            is_field: false,
1076        })))
1077    }
1078
1079    /// Produce a `self BETWEEN low AND high` range test.
1080    pub fn between(self, low: Expr, high: Expr) -> Expr {
1081        Expr(Expression::Between(Box::new(Between {
1082            this: self.0,
1083            low: low.0,
1084            high: high.0,
1085            not: false,
1086            symmetric: None,
1087        })))
1088    }
1089
1090    /// Produce a `self LIKE pattern` case-sensitive pattern match.
1091    pub fn like(self, pattern: Expr) -> Expr {
1092        Expr(Expression::Like(Box::new(LikeOp {
1093            left: self.0,
1094            right: pattern.0,
1095            escape: None,
1096            quantifier: None,
1097            inferred_type: None,
1098        })))
1099    }
1100
1101    /// Produce a `self AS alias` expression alias.
1102    pub fn alias(self, name: &str) -> Expr {
1103        alias(self, name)
1104    }
1105
1106    /// Produce a `CAST(self AS type)` type conversion.
1107    ///
1108    /// The `to` parameter is parsed as a data type name; see [`cast()`] for details.
1109    pub fn cast(self, to: &str) -> Expr {
1110        cast(self, to)
1111    }
1112
1113    /// Wrap this expression with ascending sort order (`self ASC`).
1114    ///
1115    /// Used in ORDER BY clauses. Expressions without an explicit `.asc()` or `.desc()`
1116    /// call default to ascending order when passed to [`SelectBuilder::order_by()`].
1117    pub fn asc(self) -> Expr {
1118        Expr(Expression::Ordered(Box::new(Ordered {
1119            this: self.0,
1120            desc: false,
1121            nulls_first: None,
1122            explicit_asc: true,
1123            with_fill: None,
1124        })))
1125    }
1126
1127    /// Wrap this expression with descending sort order (`self DESC`).
1128    ///
1129    /// Used in ORDER BY clauses.
1130    pub fn desc(self) -> Expr {
1131        Expr(Expression::Ordered(Box::new(Ordered {
1132            this: self.0,
1133            desc: true,
1134            nulls_first: None,
1135            explicit_asc: false,
1136            with_fill: None,
1137        })))
1138    }
1139
1140    /// Produce a `self ILIKE pattern` case-insensitive pattern match.
1141    ///
1142    /// Supported by PostgreSQL, Snowflake, and other dialects. Dialects that do not
1143    /// support `ILIKE` natively may need transpilation.
1144    pub fn ilike(self, pattern: Expr) -> Expr {
1145        Expr(Expression::ILike(Box::new(LikeOp {
1146            left: self.0,
1147            right: pattern.0,
1148            escape: None,
1149            quantifier: None,
1150            inferred_type: None,
1151        })))
1152    }
1153
1154    /// Produce a `REGEXP_LIKE(self, pattern)` regular expression match.
1155    ///
1156    /// The generated SQL uses the `REGEXP_LIKE` function form. Different dialects may
1157    /// render this as `RLIKE`, `REGEXP`, or `REGEXP_LIKE` after transpilation.
1158    pub fn rlike(self, pattern: Expr) -> Expr {
1159        Expr(Expression::RegexpLike(Box::new(RegexpFunc {
1160            this: self.0,
1161            pattern: pattern.0,
1162            flags: None,
1163        })))
1164    }
1165
1166    /// Produce a `self NOT IN (values...)` negated membership test.
1167    ///
1168    /// Each element of `values` becomes an item in the parenthesized list.
1169    pub fn not_in(self, values: impl IntoIterator<Item = Expr>) -> Expr {
1170        Expr(Expression::In(Box::new(In {
1171            this: self.0,
1172            expressions: values.into_iter().map(|v| v.0).collect(),
1173            query: None,
1174            not: true,
1175            global: false,
1176            unnest: None,
1177            is_field: false,
1178        })))
1179    }
1180}
1181
1182// ---------------------------------------------------------------------------
1183// SelectBuilder
1184// ---------------------------------------------------------------------------
1185
1186/// Fluent builder for constructing `SELECT` statements.
1187///
1188/// Created by the [`select()`] or [`from()`] entry-point functions. Methods on this
1189/// builder return `self` so they can be chained. Call [`.build()`](SelectBuilder::build)
1190/// to obtain an [`Expression`], or [`.to_sql()`](SelectBuilder::to_sql) to generate a
1191/// SQL string directly.
1192///
1193/// # Examples
1194///
1195/// ```
1196/// use polyglot_sql::builder::*;
1197///
1198/// let sql = select(["u.id", "u.name"])
1199///     .from("users")
1200///     .left_join("orders", col("u.id").eq(col("o.user_id")))
1201///     .where_(col("u.active").eq(boolean(true)))
1202///     .group_by(["u.id", "u.name"])
1203///     .order_by([col("u.name").asc()])
1204///     .limit(100)
1205///     .to_sql();
1206/// ```
1207pub struct SelectBuilder {
1208    select: Select,
1209}
1210
1211impl SelectBuilder {
1212    fn new() -> Self {
1213        SelectBuilder {
1214            select: Select::new(),
1215        }
1216    }
1217
1218    /// Append columns to the SELECT list.
1219    ///
1220    /// Accepts any iterable of [`IntoExpr`] items. This is primarily useful when the
1221    /// builder was created via [`from()`] and columns need to be added afterward.
1222    pub fn select_cols<I, E>(mut self, expressions: I) -> Self
1223    where
1224        I: IntoIterator<Item = E>,
1225        E: IntoExpr,
1226    {
1227        for expr in expressions {
1228            self.select.expressions.push(expr.into_expr().0);
1229        }
1230        self
1231    }
1232
1233    /// Set the FROM clause to reference the given table by name.
1234    pub fn from(mut self, table_name: &str) -> Self {
1235        self.select.from = Some(From {
1236            expressions: vec![Expression::Table(Box::new(builder_table_ref(table_name)))],
1237        });
1238        self
1239    }
1240
1241    /// Set the FROM clause to an arbitrary expression (e.g. a subquery or table function).
1242    ///
1243    /// Use this instead of [`SelectBuilder::from()`] when the source is not a simple
1244    /// table name -- for example, a [`subquery()`] or a table-valued function.
1245    pub fn from_expr(mut self, expr: Expr) -> Self {
1246        self.select.from = Some(From {
1247            expressions: vec![expr.0],
1248        });
1249        self
1250    }
1251
1252    /// Add an inner `JOIN` clause with the given ON condition.
1253    pub fn join(mut self, table_name: &str, on: Expr) -> Self {
1254        self.select.joins.push(Join {
1255            kind: JoinKind::Inner,
1256            this: Expression::Table(Box::new(builder_table_ref(table_name))),
1257            on: Some(on.0),
1258            using: Vec::new(),
1259            use_inner_keyword: false,
1260            use_outer_keyword: false,
1261            deferred_condition: false,
1262            join_hint: None,
1263            match_condition: None,
1264            pivots: Vec::new(),
1265            comments: Vec::new(),
1266            nesting_group: 0,
1267            directed: false,
1268        });
1269        self
1270    }
1271
1272    /// Add a `LEFT JOIN` clause with the given ON condition.
1273    pub fn left_join(mut self, table_name: &str, on: Expr) -> Self {
1274        self.select.joins.push(Join {
1275            kind: JoinKind::Left,
1276            this: Expression::Table(Box::new(builder_table_ref(table_name))),
1277            on: Some(on.0),
1278            using: Vec::new(),
1279            use_inner_keyword: false,
1280            use_outer_keyword: false,
1281            deferred_condition: false,
1282            join_hint: None,
1283            match_condition: None,
1284            pivots: Vec::new(),
1285            comments: Vec::new(),
1286            nesting_group: 0,
1287            directed: false,
1288        });
1289        self
1290    }
1291
1292    /// Set the WHERE clause to filter rows by the given condition.
1293    ///
1294    /// Calling this multiple times replaces the previous WHERE condition. To combine
1295    /// multiple predicates, chain them with [`.and()`](Expr::and) or [`.or()`](Expr::or)
1296    /// on a single [`Expr`].
1297    pub fn where_(mut self, condition: Expr) -> Self {
1298        self.select.where_clause = Some(Where { this: condition.0 });
1299        self
1300    }
1301
1302    /// Set the GROUP BY clause with the given grouping expressions.
1303    pub fn group_by<I, E>(mut self, expressions: I) -> Self
1304    where
1305        I: IntoIterator<Item = E>,
1306        E: IntoExpr,
1307    {
1308        self.select.group_by = Some(GroupBy {
1309            expressions: expressions.into_iter().map(|e| e.into_expr().0).collect(),
1310            all: None,
1311            totals: false,
1312            comments: Vec::new(),
1313        });
1314        self
1315    }
1316
1317    /// Set the HAVING clause to filter groups by the given condition.
1318    pub fn having(mut self, condition: Expr) -> Self {
1319        self.select.having = Some(Having {
1320            this: condition.0,
1321            comments: Vec::new(),
1322        });
1323        self
1324    }
1325
1326    /// Set the ORDER BY clause with the given sort expressions.
1327    ///
1328    /// Expressions that are not already wrapped with [`.asc()`](Expr::asc) or
1329    /// [`.desc()`](Expr::desc) default to ascending order. String values are
1330    /// interpreted as column names via [`IntoExpr`].
1331    pub fn order_by<I, E>(mut self, expressions: I) -> Self
1332    where
1333        I: IntoIterator<Item = E>,
1334        E: IntoExpr,
1335    {
1336        self.select.order_by = Some(OrderBy {
1337            siblings: false,
1338            comments: Vec::new(),
1339            expressions: expressions
1340                .into_iter()
1341                .map(|e| {
1342                    let expr = e.into_expr().0;
1343                    match expr {
1344                        Expression::Ordered(_) => expr,
1345                        other => Expression::Ordered(Box::new(Ordered {
1346                            this: other,
1347                            desc: false,
1348                            nulls_first: None,
1349                            explicit_asc: false,
1350                            with_fill: None,
1351                        })),
1352                    }
1353                })
1354                .collect::<Vec<_>>()
1355                .into_iter()
1356                .map(|e| {
1357                    if let Expression::Ordered(o) = e {
1358                        *o
1359                    } else {
1360                        Ordered {
1361                            this: e,
1362                            desc: false,
1363                            nulls_first: None,
1364                            explicit_asc: false,
1365                            with_fill: None,
1366                        }
1367                    }
1368                })
1369                .collect(),
1370        });
1371        self
1372    }
1373
1374    /// Set the SORT BY clause with the given sort expressions.
1375    ///
1376    /// SORT BY is used in Hive/Spark to sort data within each reducer (partition),
1377    /// as opposed to ORDER BY which sorts globally. Expressions that are not already
1378    /// wrapped with [`.asc()`](Expr::asc) or [`.desc()`](Expr::desc) default to
1379    /// ascending order.
1380    pub fn sort_by<I, E>(mut self, expressions: I) -> Self
1381    where
1382        I: IntoIterator<Item = E>,
1383        E: IntoExpr,
1384    {
1385        self.select.sort_by = Some(SortBy {
1386            expressions: expressions
1387                .into_iter()
1388                .map(|e| {
1389                    let expr = e.into_expr().0;
1390                    match expr {
1391                        Expression::Ordered(o) => *o,
1392                        other => Ordered {
1393                            this: other,
1394                            desc: false,
1395                            nulls_first: None,
1396                            explicit_asc: false,
1397                            with_fill: None,
1398                        },
1399                    }
1400                })
1401                .collect(),
1402        });
1403        self
1404    }
1405
1406    /// Set the LIMIT clause to restrict the result set to `count` rows.
1407    pub fn limit(mut self, count: usize) -> Self {
1408        self.select.limit = Some(Limit {
1409            this: Expression::Literal(Box::new(Literal::Number(count.to_string()))),
1410            percent: false,
1411            comments: Vec::new(),
1412        });
1413        self
1414    }
1415
1416    /// Set the OFFSET clause to skip the first `count` rows.
1417    pub fn offset(mut self, count: usize) -> Self {
1418        self.select.offset = Some(Offset {
1419            this: Expression::Literal(Box::new(Literal::Number(count.to_string()))),
1420            rows: None,
1421        });
1422        self
1423    }
1424
1425    /// Enable the DISTINCT modifier on the SELECT clause.
1426    pub fn distinct(mut self) -> Self {
1427        self.select.distinct = true;
1428        self
1429    }
1430
1431    /// Add a QUALIFY clause to filter rows after window function evaluation.
1432    ///
1433    /// QUALIFY is supported by Snowflake, BigQuery, DuckDB, and Databricks. It acts
1434    /// like a WHERE clause but is applied after window functions are computed.
1435    pub fn qualify(mut self, condition: Expr) -> Self {
1436        self.select.qualify = Some(Qualify { this: condition.0 });
1437        self
1438    }
1439
1440    /// Add a `RIGHT JOIN` clause with the given ON condition.
1441    pub fn right_join(mut self, table_name: &str, on: Expr) -> Self {
1442        self.select.joins.push(Join {
1443            kind: JoinKind::Right,
1444            this: Expression::Table(Box::new(builder_table_ref(table_name))),
1445            on: Some(on.0),
1446            using: Vec::new(),
1447            use_inner_keyword: false,
1448            use_outer_keyword: false,
1449            deferred_condition: false,
1450            join_hint: None,
1451            match_condition: None,
1452            pivots: Vec::new(),
1453            comments: Vec::new(),
1454            nesting_group: 0,
1455            directed: false,
1456        });
1457        self
1458    }
1459
1460    /// Add a `CROSS JOIN` clause (Cartesian product, no ON condition).
1461    pub fn cross_join(mut self, table_name: &str) -> Self {
1462        self.select.joins.push(Join {
1463            kind: JoinKind::Cross,
1464            this: Expression::Table(Box::new(builder_table_ref(table_name))),
1465            on: None,
1466            using: Vec::new(),
1467            use_inner_keyword: false,
1468            use_outer_keyword: false,
1469            deferred_condition: false,
1470            join_hint: None,
1471            match_condition: None,
1472            pivots: Vec::new(),
1473            comments: Vec::new(),
1474            nesting_group: 0,
1475            directed: false,
1476        });
1477        self
1478    }
1479
1480    /// Add a `LATERAL VIEW` clause for Hive/Spark user-defined table function (UDTF)
1481    /// expansion.
1482    ///
1483    /// `table_function` is the UDTF expression (e.g. `func("EXPLODE", [col("arr")])`),
1484    /// `table_alias` names the virtual table, and `column_aliases` name the output
1485    /// columns produced by the function.
1486    pub fn lateral_view<S: AsRef<str>>(
1487        mut self,
1488        table_function: Expr,
1489        table_alias: &str,
1490        column_aliases: impl IntoIterator<Item = S>,
1491    ) -> Self {
1492        self.select.lateral_views.push(LateralView {
1493            this: table_function.0,
1494            table_alias: Some(builder_identifier(table_alias)),
1495            column_aliases: column_aliases
1496                .into_iter()
1497                .map(|c| builder_identifier(c.as_ref()))
1498                .collect(),
1499            outer: false,
1500        });
1501        self
1502    }
1503
1504    /// Add a named `WINDOW` clause definition.
1505    ///
1506    /// The window `name` can then be referenced in window function OVER clauses
1507    /// elsewhere in the query. The definition is constructed via [`WindowDefBuilder`].
1508    /// Multiple calls append additional named windows.
1509    pub fn window(mut self, name: &str, def: WindowDefBuilder) -> Self {
1510        let named_window = NamedWindow {
1511            name: builder_identifier(name),
1512            spec: Over {
1513                window_name: None,
1514                partition_by: def.partition_by,
1515                order_by: def.order_by,
1516                frame: None,
1517                alias: None,
1518            },
1519        };
1520        match self.select.windows {
1521            Some(ref mut windows) => windows.push(named_window),
1522            None => self.select.windows = Some(vec![named_window]),
1523        }
1524        self
1525    }
1526
1527    /// Add a `FOR UPDATE` locking clause.
1528    ///
1529    /// Appends a `FOR UPDATE` lock to the SELECT statement. This is used by
1530    /// databases (PostgreSQL, MySQL, Oracle) to lock selected rows for update.
1531    pub fn for_update(mut self) -> Self {
1532        self.select.locks.push(Lock {
1533            update: Some(Box::new(Expression::Boolean(BooleanLiteral {
1534                value: true,
1535            }))),
1536            expressions: vec![],
1537            wait: None,
1538            key: None,
1539        });
1540        self
1541    }
1542
1543    /// Add a `FOR SHARE` locking clause.
1544    ///
1545    /// Appends a `FOR SHARE` lock to the SELECT statement. This allows other
1546    /// transactions to read the locked rows but prevents updates.
1547    pub fn for_share(mut self) -> Self {
1548        self.select.locks.push(Lock {
1549            update: None,
1550            expressions: vec![],
1551            wait: None,
1552            key: None,
1553        });
1554        self
1555    }
1556
1557    /// Add a query hint (e.g., Oracle `/*+ FULL(t) */`).
1558    ///
1559    /// Hints are rendered for Oracle, MySQL, Spark, Hive, Databricks, and PostgreSQL
1560    /// dialects. Multiple calls append additional hints.
1561    pub fn hint(mut self, hint_text: &str) -> Self {
1562        let hint_expr = HintExpression::Raw(hint_text.to_string());
1563        match &mut self.select.hint {
1564            Some(h) => h.expressions.push(hint_expr),
1565            None => {
1566                self.select.hint = Some(Hint {
1567                    expressions: vec![hint_expr],
1568                })
1569            }
1570        }
1571        self
1572    }
1573
1574    /// Convert this SELECT into a `CREATE TABLE AS SELECT` statement.
1575    ///
1576    /// Consumes the builder and returns an [`Expression::CreateTable`] with this
1577    /// query as the `as_select` source.
1578    ///
1579    /// # Examples
1580    ///
1581    /// ```
1582    /// use polyglot_sql::builder::*;
1583    ///
1584    /// let sql = polyglot_sql::generator::Generator::sql(
1585    ///     &select(["*"]).from("t").ctas("new_table")
1586    /// ).unwrap();
1587    /// assert_eq!(sql, "CREATE TABLE new_table AS SELECT * FROM t");
1588    /// ```
1589    pub fn ctas(self, table_name: &str) -> Expression {
1590        Expression::CreateTable(Box::new(CreateTable {
1591            name: builder_table_ref(table_name),
1592            on_cluster: None,
1593            columns: vec![],
1594            constraints: vec![],
1595            if_not_exists: false,
1596            temporary: false,
1597            or_replace: false,
1598            table_modifier: None,
1599            as_select: Some(self.build()),
1600            as_select_parenthesized: false,
1601            on_commit: None,
1602            clone_source: None,
1603            clone_at_clause: None,
1604            is_copy: false,
1605            shallow_clone: false,
1606            leading_comments: vec![],
1607            with_properties: vec![],
1608            teradata_post_name_options: vec![],
1609            with_data: None,
1610            with_statistics: None,
1611            teradata_indexes: vec![],
1612            with_cte: None,
1613            properties: vec![],
1614            partition_of: None,
1615            post_table_properties: vec![],
1616            mysql_table_options: vec![],
1617            inherits: vec![],
1618            on_property: None,
1619            copy_grants: false,
1620            using_template: None,
1621            rollup: None,
1622            uuid: None,
1623            with_partition_columns: vec![],
1624            with_connection: None,
1625        }))
1626    }
1627
1628    /// Combine this SELECT with another via `UNION` (duplicate elimination).
1629    ///
1630    /// Returns a [`SetOpBuilder`] for further chaining (e.g. `.order_by()`, `.limit()`).
1631    pub fn union(self, other: SelectBuilder) -> SetOpBuilder {
1632        SetOpBuilder::new(SetOpKind::Union, self, other, false)
1633    }
1634
1635    /// Combine this SELECT with another via `UNION ALL` (keep duplicates).
1636    ///
1637    /// Returns a [`SetOpBuilder`] for further chaining.
1638    pub fn union_all(self, other: SelectBuilder) -> SetOpBuilder {
1639        SetOpBuilder::new(SetOpKind::Union, self, other, true)
1640    }
1641
1642    /// Combine this SELECT with another via `INTERSECT` (rows common to both).
1643    ///
1644    /// Returns a [`SetOpBuilder`] for further chaining.
1645    pub fn intersect(self, other: SelectBuilder) -> SetOpBuilder {
1646        SetOpBuilder::new(SetOpKind::Intersect, self, other, false)
1647    }
1648
1649    /// Combine this SELECT with another via `EXCEPT` (rows in left but not right).
1650    ///
1651    /// Returns a [`SetOpBuilder`] for further chaining.
1652    pub fn except_(self, other: SelectBuilder) -> SetOpBuilder {
1653        SetOpBuilder::new(SetOpKind::Except, self, other, false)
1654    }
1655
1656    /// Consume this builder and produce the final [`Expression::Select`] AST node.
1657    pub fn build(self) -> Expression {
1658        Expression::Select(Box::new(self.select))
1659    }
1660
1661    /// Consume this builder, generate, and return the SQL string.
1662    ///
1663    /// Equivalent to calling `.build()` followed by [`Generator::sql()`]. Returns an
1664    /// empty string if generation fails.
1665    pub fn to_sql(self) -> String {
1666        Generator::sql(&self.build()).unwrap_or_default()
1667    }
1668}
1669
1670// ---------------------------------------------------------------------------
1671// DeleteBuilder
1672// ---------------------------------------------------------------------------
1673
1674/// Fluent builder for constructing `DELETE FROM` statements.
1675///
1676/// Created by the [`delete()`] entry-point function. Supports an optional `.where_()`
1677/// predicate.
1678pub struct DeleteBuilder {
1679    delete: Delete,
1680}
1681
1682impl DeleteBuilder {
1683    /// Set the WHERE clause to restrict which rows are deleted.
1684    pub fn where_(mut self, condition: Expr) -> Self {
1685        self.delete.where_clause = Some(Where { this: condition.0 });
1686        self
1687    }
1688
1689    /// Consume this builder and produce the final [`Expression::Delete`] AST node.
1690    pub fn build(self) -> Expression {
1691        Expression::Delete(Box::new(self.delete))
1692    }
1693
1694    /// Consume this builder, generate, and return the SQL string.
1695    pub fn to_sql(self) -> String {
1696        Generator::sql(&self.build()).unwrap_or_default()
1697    }
1698}
1699
1700// ---------------------------------------------------------------------------
1701// InsertBuilder
1702// ---------------------------------------------------------------------------
1703
1704/// Fluent builder for constructing `INSERT INTO` statements.
1705///
1706/// Created by the [`insert_into()`] entry-point function. Supports specifying target
1707/// columns via [`.columns()`](InsertBuilder::columns), row values via
1708/// [`.values()`](InsertBuilder::values) (can be called multiple times for multiple rows),
1709/// and INSERT ... SELECT via [`.query()`](InsertBuilder::query).
1710pub struct InsertBuilder {
1711    insert: Insert,
1712}
1713
1714impl InsertBuilder {
1715    /// Set the target column names for the INSERT statement.
1716    pub fn columns<I, S>(mut self, columns: I) -> Self
1717    where
1718        I: IntoIterator<Item = S>,
1719        S: AsRef<str>,
1720    {
1721        self.insert.columns = columns
1722            .into_iter()
1723            .map(|c| builder_identifier(c.as_ref()))
1724            .collect();
1725        self
1726    }
1727
1728    /// Append a row of values to the VALUES clause.
1729    ///
1730    /// Call this method multiple times to insert multiple rows in a single statement.
1731    pub fn values<I>(mut self, values: I) -> Self
1732    where
1733        I: IntoIterator<Item = Expr>,
1734    {
1735        self.insert
1736            .values
1737            .push(values.into_iter().map(|v| v.0).collect());
1738        self
1739    }
1740
1741    /// Set the source query for an `INSERT INTO ... SELECT ...` statement.
1742    ///
1743    /// When a query is set, the VALUES clause is ignored during generation.
1744    pub fn query(mut self, query: SelectBuilder) -> Self {
1745        self.insert.query = Some(query.build());
1746        self
1747    }
1748
1749    /// Consume this builder and produce the final [`Expression::Insert`] AST node.
1750    pub fn build(self) -> Expression {
1751        Expression::Insert(Box::new(self.insert))
1752    }
1753
1754    /// Consume this builder, generate, and return the SQL string.
1755    pub fn to_sql(self) -> String {
1756        Generator::sql(&self.build()).unwrap_or_default()
1757    }
1758}
1759
1760// ---------------------------------------------------------------------------
1761// UpdateBuilder
1762// ---------------------------------------------------------------------------
1763
1764/// Fluent builder for constructing `UPDATE` statements.
1765///
1766/// Created by the [`update()`] entry-point function. Supports column assignments via
1767/// [`.set()`](UpdateBuilder::set), an optional WHERE predicate, and an optional
1768/// FROM clause for PostgreSQL/Snowflake-style multi-table updates.
1769pub struct UpdateBuilder {
1770    update: Update,
1771}
1772
1773impl UpdateBuilder {
1774    /// Add a `SET column = value` assignment.
1775    ///
1776    /// Call this method multiple times to set multiple columns.
1777    pub fn set(mut self, column: &str, value: Expr) -> Self {
1778        self.update.set.push((builder_identifier(column), value.0));
1779        self
1780    }
1781
1782    /// Set the WHERE clause to restrict which rows are updated.
1783    pub fn where_(mut self, condition: Expr) -> Self {
1784        self.update.where_clause = Some(Where { this: condition.0 });
1785        self
1786    }
1787
1788    /// Set the FROM clause for PostgreSQL/Snowflake-style `UPDATE ... FROM ...` syntax.
1789    ///
1790    /// This allows joining against other tables within the UPDATE statement.
1791    pub fn from(mut self, table_name: &str) -> Self {
1792        self.update.from_clause = Some(From {
1793            expressions: vec![Expression::Table(Box::new(builder_table_ref(table_name)))],
1794        });
1795        self
1796    }
1797
1798    /// Consume this builder and produce the final [`Expression::Update`] AST node.
1799    pub fn build(self) -> Expression {
1800        Expression::Update(Box::new(self.update))
1801    }
1802
1803    /// Consume this builder, generate, and return the SQL string.
1804    pub fn to_sql(self) -> String {
1805        Generator::sql(&self.build()).unwrap_or_default()
1806    }
1807}
1808
1809// ---------------------------------------------------------------------------
1810// CaseBuilder
1811// ---------------------------------------------------------------------------
1812
1813/// Start building a searched CASE expression (`CASE WHEN cond THEN result ... END`).
1814///
1815/// A searched CASE evaluates each WHEN condition independently. Use [`case_of()`] for
1816/// a simple CASE that compares an operand against values.
1817///
1818/// # Examples
1819///
1820/// ```
1821/// use polyglot_sql::builder::*;
1822///
1823/// let expr = case()
1824///     .when(col("x").gt(lit(0)), lit("positive"))
1825///     .when(col("x").eq(lit(0)), lit("zero"))
1826///     .else_(lit("negative"))
1827///     .build();
1828/// assert_eq!(
1829///     expr.to_sql(),
1830///     "CASE WHEN x > 0 THEN 'positive' WHEN x = 0 THEN 'zero' ELSE 'negative' END"
1831/// );
1832/// ```
1833pub fn case() -> CaseBuilder {
1834    CaseBuilder {
1835        operand: None,
1836        whens: Vec::new(),
1837        else_: None,
1838    }
1839}
1840
1841/// Start building a simple CASE expression (`CASE operand WHEN value THEN result ... END`).
1842///
1843/// A simple CASE compares the `operand` against each WHEN value for equality. Use
1844/// [`case()`] for a searched CASE with arbitrary boolean conditions.
1845///
1846/// # Examples
1847///
1848/// ```
1849/// use polyglot_sql::builder::*;
1850///
1851/// let expr = case_of(col("status"))
1852///     .when(lit(1), lit("active"))
1853///     .when(lit(0), lit("inactive"))
1854///     .else_(lit("unknown"))
1855///     .build();
1856/// assert_eq!(
1857///     expr.to_sql(),
1858///     "CASE status WHEN 1 THEN 'active' WHEN 0 THEN 'inactive' ELSE 'unknown' END"
1859/// );
1860/// ```
1861pub fn case_of(operand: Expr) -> CaseBuilder {
1862    CaseBuilder {
1863        operand: Some(operand.0),
1864        whens: Vec::new(),
1865        else_: None,
1866    }
1867}
1868
1869/// Fluent builder for SQL `CASE` expressions (both searched and simple forms).
1870///
1871/// Created by [`case()`] (searched form) or [`case_of()`] (simple form). Add branches
1872/// with [`.when()`](CaseBuilder::when) and an optional default with
1873/// [`.else_()`](CaseBuilder::else_). Finalize with [`.build()`](CaseBuilder::build) to
1874/// get an [`Expr`], or [`.build_expr()`](CaseBuilder::build_expr) for a raw
1875/// [`Expression`].
1876pub struct CaseBuilder {
1877    operand: Option<Expression>,
1878    whens: Vec<(Expression, Expression)>,
1879    else_: Option<Expression>,
1880}
1881
1882impl CaseBuilder {
1883    /// Add a `WHEN condition THEN result` branch to the CASE expression.
1884    ///
1885    /// For a searched CASE ([`case()`]), `condition` is a boolean predicate. For a simple
1886    /// CASE ([`case_of()`]), `condition` is the value to compare against the operand.
1887    pub fn when(mut self, condition: Expr, result: Expr) -> Self {
1888        self.whens.push((condition.0, result.0));
1889        self
1890    }
1891
1892    /// Set the `ELSE result` default branch of the CASE expression.
1893    ///
1894    /// If not called, the CASE expression has no ELSE clause (implicitly NULL when
1895    /// no WHEN matches).
1896    pub fn else_(mut self, result: Expr) -> Self {
1897        self.else_ = Some(result.0);
1898        self
1899    }
1900
1901    /// Consume this builder and produce an [`Expr`] wrapping the CASE expression.
1902    pub fn build(self) -> Expr {
1903        Expr(self.build_expr())
1904    }
1905
1906    /// Consume this builder and produce the raw [`Expression::Case`] AST node.
1907    ///
1908    /// Use this instead of [`.build()`](CaseBuilder::build) when you need the
1909    /// [`Expression`] directly rather than an [`Expr`] wrapper.
1910    pub fn build_expr(self) -> Expression {
1911        Expression::Case(Box::new(Case {
1912            operand: self.operand,
1913            whens: self.whens,
1914            else_: self.else_,
1915            comments: Vec::new(),
1916            inferred_type: None,
1917        }))
1918    }
1919}
1920
1921// ---------------------------------------------------------------------------
1922// Subquery builders
1923// ---------------------------------------------------------------------------
1924
1925/// Wrap a [`SelectBuilder`] as a named subquery for use in FROM or JOIN clauses.
1926///
1927/// The resulting [`Expr`] can be passed to [`SelectBuilder::from_expr()`] or used
1928/// in a join condition.
1929///
1930/// # Examples
1931///
1932/// ```
1933/// use polyglot_sql::builder::*;
1934///
1935/// let inner = select(["id", "name"]).from("users").where_(col("active").eq(boolean(true)));
1936/// let sql = select(["sub.id"])
1937///     .from_expr(subquery(inner, "sub"))
1938///     .to_sql();
1939/// assert_eq!(
1940///     sql,
1941///     "SELECT sub.id FROM (SELECT id, name FROM users WHERE active = TRUE) AS sub"
1942/// );
1943/// ```
1944pub fn subquery(query: SelectBuilder, alias_name: &str) -> Expr {
1945    subquery_expr(query.build(), alias_name)
1946}
1947
1948/// Wrap an existing [`Expression`] as a named subquery.
1949///
1950/// This is the lower-level version of [`subquery()`] that accepts a pre-built
1951/// [`Expression`] instead of a [`SelectBuilder`].
1952pub fn subquery_expr(expr: Expression, alias_name: &str) -> Expr {
1953    Expr(Expression::Subquery(Box::new(Subquery {
1954        this: expr,
1955        alias: Some(builder_identifier(alias_name)),
1956        column_aliases: Vec::new(),
1957        order_by: None,
1958        limit: None,
1959        offset: None,
1960        distribute_by: None,
1961        sort_by: None,
1962        cluster_by: None,
1963        lateral: false,
1964        modifiers_inside: true,
1965        trailing_comments: Vec::new(),
1966        inferred_type: None,
1967    })))
1968}
1969
1970// ---------------------------------------------------------------------------
1971// SetOpBuilder
1972// ---------------------------------------------------------------------------
1973
1974/// Internal enum distinguishing the three kinds of set operations.
1975#[derive(Debug, Clone, Copy)]
1976enum SetOpKind {
1977    Union,
1978    Intersect,
1979    Except,
1980}
1981
1982/// Fluent builder for `UNION`, `INTERSECT`, and `EXCEPT` set operations.
1983///
1984/// Created by the free functions [`union()`], [`union_all()`], [`intersect()`],
1985/// [`intersect_all()`], [`except_()`], [`except_all()`], or the corresponding methods
1986/// on [`SelectBuilder`]. Supports optional `.order_by()`, `.limit()`, and `.offset()`
1987/// clauses applied to the combined result.
1988///
1989/// # Examples
1990///
1991/// ```
1992/// use polyglot_sql::builder::*;
1993///
1994/// let sql = union_all(
1995///     select(["id"]).from("a"),
1996///     select(["id"]).from("b"),
1997/// )
1998/// .order_by(["id"])
1999/// .limit(10)
2000/// .to_sql();
2001/// ```
2002pub struct SetOpBuilder {
2003    kind: SetOpKind,
2004    left: Expression,
2005    right: Expression,
2006    all: bool,
2007    order_by: Option<OrderBy>,
2008    limit: Option<Box<Expression>>,
2009    offset: Option<Box<Expression>>,
2010}
2011
2012impl SetOpBuilder {
2013    fn new(kind: SetOpKind, left: SelectBuilder, right: SelectBuilder, all: bool) -> Self {
2014        SetOpBuilder {
2015            kind,
2016            left: left.build(),
2017            right: right.build(),
2018            all,
2019            order_by: None,
2020            limit: None,
2021            offset: None,
2022        }
2023    }
2024
2025    /// Add an ORDER BY clause applied to the combined set operation result.
2026    ///
2027    /// Expressions not already wrapped with [`.asc()`](Expr::asc) or
2028    /// [`.desc()`](Expr::desc) default to ascending order.
2029    pub fn order_by<I, E>(mut self, expressions: I) -> Self
2030    where
2031        I: IntoIterator<Item = E>,
2032        E: IntoExpr,
2033    {
2034        self.order_by = Some(OrderBy {
2035            siblings: false,
2036            comments: Vec::new(),
2037            expressions: expressions
2038                .into_iter()
2039                .map(|e| {
2040                    let expr = e.into_expr().0;
2041                    match expr {
2042                        Expression::Ordered(o) => *o,
2043                        other => Ordered {
2044                            this: other,
2045                            desc: false,
2046                            nulls_first: None,
2047                            explicit_asc: false,
2048                            with_fill: None,
2049                        },
2050                    }
2051                })
2052                .collect(),
2053        });
2054        self
2055    }
2056
2057    /// Restrict the combined set operation result to `count` rows.
2058    pub fn limit(mut self, count: usize) -> Self {
2059        self.limit = Some(Box::new(Expression::Literal(Box::new(Literal::Number(
2060            count.to_string(),
2061        )))));
2062        self
2063    }
2064
2065    /// Skip the first `count` rows from the combined set operation result.
2066    pub fn offset(mut self, count: usize) -> Self {
2067        self.offset = Some(Box::new(Expression::Literal(Box::new(Literal::Number(
2068            count.to_string(),
2069        )))));
2070        self
2071    }
2072
2073    /// Consume this builder and produce the final set operation [`Expression`] AST node.
2074    ///
2075    /// The returned expression is one of [`Expression::Union`], [`Expression::Intersect`],
2076    /// or [`Expression::Except`] depending on how the builder was created.
2077    pub fn build(self) -> Expression {
2078        match self.kind {
2079            SetOpKind::Union => Expression::Union(Box::new(Union {
2080                left: self.left,
2081                right: self.right,
2082                all: self.all,
2083                distinct: false,
2084                with: None,
2085                order_by: self.order_by,
2086                limit: self.limit,
2087                offset: self.offset,
2088                distribute_by: None,
2089                sort_by: None,
2090                cluster_by: None,
2091                by_name: false,
2092                side: None,
2093                kind: None,
2094                corresponding: false,
2095                strict: false,
2096                on_columns: Vec::new(),
2097            })),
2098            SetOpKind::Intersect => Expression::Intersect(Box::new(Intersect {
2099                left: self.left,
2100                right: self.right,
2101                all: self.all,
2102                distinct: false,
2103                with: None,
2104                order_by: self.order_by,
2105                limit: self.limit,
2106                offset: self.offset,
2107                distribute_by: None,
2108                sort_by: None,
2109                cluster_by: None,
2110                by_name: false,
2111                side: None,
2112                kind: None,
2113                corresponding: false,
2114                strict: false,
2115                on_columns: Vec::new(),
2116            })),
2117            SetOpKind::Except => Expression::Except(Box::new(Except {
2118                left: self.left,
2119                right: self.right,
2120                all: self.all,
2121                distinct: false,
2122                with: None,
2123                order_by: self.order_by,
2124                limit: self.limit,
2125                offset: self.offset,
2126                distribute_by: None,
2127                sort_by: None,
2128                cluster_by: None,
2129                by_name: false,
2130                side: None,
2131                kind: None,
2132                corresponding: false,
2133                strict: false,
2134                on_columns: Vec::new(),
2135            })),
2136        }
2137    }
2138
2139    /// Consume this builder, generate, and return the SQL string.
2140    pub fn to_sql(self) -> String {
2141        Generator::sql(&self.build()).unwrap_or_default()
2142    }
2143}
2144
2145/// Create a `UNION` (duplicate elimination) of two SELECT queries.
2146///
2147/// Returns a [`SetOpBuilder`] for optional ORDER BY / LIMIT / OFFSET chaining.
2148pub fn union(left: SelectBuilder, right: SelectBuilder) -> SetOpBuilder {
2149    SetOpBuilder::new(SetOpKind::Union, left, right, false)
2150}
2151
2152/// Create a `UNION ALL` (keep duplicates) of two SELECT queries.
2153///
2154/// Returns a [`SetOpBuilder`] for optional ORDER BY / LIMIT / OFFSET chaining.
2155pub fn union_all(left: SelectBuilder, right: SelectBuilder) -> SetOpBuilder {
2156    SetOpBuilder::new(SetOpKind::Union, left, right, true)
2157}
2158
2159/// Create an `INTERSECT` (rows common to both) of two SELECT queries.
2160///
2161/// Returns a [`SetOpBuilder`] for optional ORDER BY / LIMIT / OFFSET chaining.
2162pub fn intersect(left: SelectBuilder, right: SelectBuilder) -> SetOpBuilder {
2163    SetOpBuilder::new(SetOpKind::Intersect, left, right, false)
2164}
2165
2166/// Create an `INTERSECT ALL` (keep duplicate common rows) of two SELECT queries.
2167///
2168/// Returns a [`SetOpBuilder`] for optional ORDER BY / LIMIT / OFFSET chaining.
2169pub fn intersect_all(left: SelectBuilder, right: SelectBuilder) -> SetOpBuilder {
2170    SetOpBuilder::new(SetOpKind::Intersect, left, right, true)
2171}
2172
2173/// Create an `EXCEPT` (rows in left but not right) of two SELECT queries.
2174///
2175/// Returns a [`SetOpBuilder`] for optional ORDER BY / LIMIT / OFFSET chaining.
2176pub fn except_(left: SelectBuilder, right: SelectBuilder) -> SetOpBuilder {
2177    SetOpBuilder::new(SetOpKind::Except, left, right, false)
2178}
2179
2180/// Create an `EXCEPT ALL` (keep duplicate difference rows) of two SELECT queries.
2181///
2182/// Returns a [`SetOpBuilder`] for optional ORDER BY / LIMIT / OFFSET chaining.
2183pub fn except_all(left: SelectBuilder, right: SelectBuilder) -> SetOpBuilder {
2184    SetOpBuilder::new(SetOpKind::Except, left, right, true)
2185}
2186
2187// ---------------------------------------------------------------------------
2188// WindowDefBuilder
2189// ---------------------------------------------------------------------------
2190
2191/// Builder for constructing named `WINDOW` clause definitions.
2192///
2193/// Used with [`SelectBuilder::window()`] to define reusable window specifications.
2194/// Supports PARTITION BY and ORDER BY clauses.
2195///
2196/// # Examples
2197///
2198/// ```
2199/// use polyglot_sql::builder::*;
2200///
2201/// let sql = select(["id"])
2202///     .from("t")
2203///     .window(
2204///         "w",
2205///         WindowDefBuilder::new()
2206///             .partition_by(["dept"])
2207///             .order_by([col("salary").desc()]),
2208///     )
2209///     .to_sql();
2210/// ```
2211pub struct WindowDefBuilder {
2212    partition_by: Vec<Expression>,
2213    order_by: Vec<Ordered>,
2214}
2215
2216impl WindowDefBuilder {
2217    /// Create a new, empty window definition builder with no partitioning or ordering.
2218    pub fn new() -> Self {
2219        WindowDefBuilder {
2220            partition_by: Vec::new(),
2221            order_by: Vec::new(),
2222        }
2223    }
2224
2225    /// Set the PARTITION BY expressions for the window definition.
2226    pub fn partition_by<I, E>(mut self, expressions: I) -> Self
2227    where
2228        I: IntoIterator<Item = E>,
2229        E: IntoExpr,
2230    {
2231        self.partition_by = expressions.into_iter().map(|e| e.into_expr().0).collect();
2232        self
2233    }
2234
2235    /// Set the ORDER BY expressions for the window definition.
2236    ///
2237    /// Expressions not already wrapped with [`.asc()`](Expr::asc) or
2238    /// [`.desc()`](Expr::desc) default to ascending order.
2239    pub fn order_by<I, E>(mut self, expressions: I) -> Self
2240    where
2241        I: IntoIterator<Item = E>,
2242        E: IntoExpr,
2243    {
2244        self.order_by = expressions
2245            .into_iter()
2246            .map(|e| {
2247                let expr = e.into_expr().0;
2248                match expr {
2249                    Expression::Ordered(o) => *o,
2250                    other => Ordered {
2251                        this: other,
2252                        desc: false,
2253                        nulls_first: None,
2254                        explicit_asc: false,
2255                        with_fill: None,
2256                    },
2257                }
2258            })
2259            .collect();
2260        self
2261    }
2262}
2263
2264// ---------------------------------------------------------------------------
2265// Trait: IntoExpr
2266// ---------------------------------------------------------------------------
2267
2268/// Conversion trait for types that can be turned into an [`Expr`].
2269///
2270/// This trait is implemented for:
2271///
2272/// - [`Expr`] -- returned as-is.
2273/// - `&str` and `String` -- converted to a column reference via [`col()`].
2274/// - [`Expression`] -- wrapped directly in an [`Expr`].
2275///
2276/// Note: `&str`/`String` inputs are treated as identifiers, not SQL string
2277/// literals. Use [`lit()`] for literal values.
2278///
2279/// It is used as a generic bound throughout the builder API so that functions like
2280/// [`select()`], [`SelectBuilder::order_by()`], and [`SelectBuilder::group_by()`] can
2281/// accept plain strings, [`Expr`] values, or raw [`Expression`] nodes interchangeably.
2282pub trait IntoExpr {
2283    /// Convert this value into an [`Expr`].
2284    fn into_expr(self) -> Expr;
2285}
2286
2287impl IntoExpr for Expr {
2288    fn into_expr(self) -> Expr {
2289        self
2290    }
2291}
2292
2293impl IntoExpr for &str {
2294    /// Convert a string slice to a column reference via [`col()`].
2295    fn into_expr(self) -> Expr {
2296        col(self)
2297    }
2298}
2299
2300impl IntoExpr for String {
2301    /// Convert an owned string to a column reference via [`col()`].
2302    fn into_expr(self) -> Expr {
2303        col(&self)
2304    }
2305}
2306
2307impl IntoExpr for Expression {
2308    /// Wrap a raw [`Expression`] in an [`Expr`].
2309    fn into_expr(self) -> Expr {
2310        Expr(self)
2311    }
2312}
2313
2314// ---------------------------------------------------------------------------
2315// Trait: IntoLiteral
2316// ---------------------------------------------------------------------------
2317
2318/// Conversion trait for types that can be turned into a SQL literal [`Expr`].
2319///
2320/// This trait is used by [`lit()`] to accept various Rust primitive types and convert
2321/// them into the appropriate SQL literal representation.
2322///
2323/// Implemented for:
2324///
2325/// - `&str`, `String` -- produce a SQL string literal (e.g. `'hello'`).
2326/// - `i32`, `i64`, `usize`, `f64` -- produce a SQL numeric literal (e.g. `42`, `3.14`).
2327/// - `bool` -- produce a SQL boolean literal (`TRUE` or `FALSE`).
2328pub trait IntoLiteral {
2329    /// Convert this value into a literal [`Expr`].
2330    fn into_literal(self) -> Expr;
2331}
2332
2333impl IntoLiteral for &str {
2334    /// Produce a SQL string literal (e.g. `'hello'`).
2335    fn into_literal(self) -> Expr {
2336        Expr(Expression::Literal(Box::new(Literal::String(
2337            self.to_string(),
2338        ))))
2339    }
2340}
2341
2342impl IntoLiteral for String {
2343    /// Produce a SQL string literal from an owned string.
2344    fn into_literal(self) -> Expr {
2345        Expr(Expression::Literal(Box::new(Literal::String(self))))
2346    }
2347}
2348
2349impl IntoLiteral for i64 {
2350    /// Produce a SQL numeric literal from a 64-bit integer.
2351    fn into_literal(self) -> Expr {
2352        Expr(Expression::Literal(Box::new(Literal::Number(
2353            self.to_string(),
2354        ))))
2355    }
2356}
2357
2358impl IntoLiteral for i32 {
2359    /// Produce a SQL numeric literal from a 32-bit integer.
2360    fn into_literal(self) -> Expr {
2361        Expr(Expression::Literal(Box::new(Literal::Number(
2362            self.to_string(),
2363        ))))
2364    }
2365}
2366
2367impl IntoLiteral for usize {
2368    /// Produce a SQL numeric literal from a `usize`.
2369    fn into_literal(self) -> Expr {
2370        Expr(Expression::Literal(Box::new(Literal::Number(
2371            self.to_string(),
2372        ))))
2373    }
2374}
2375
2376impl IntoLiteral for f64 {
2377    /// Produce a SQL numeric literal from a 64-bit float.
2378    fn into_literal(self) -> Expr {
2379        Expr(Expression::Literal(Box::new(Literal::Number(
2380            self.to_string(),
2381        ))))
2382    }
2383}
2384
2385impl IntoLiteral for bool {
2386    /// Produce a SQL boolean literal (`TRUE` or `FALSE`).
2387    fn into_literal(self) -> Expr {
2388        Expr(Expression::Boolean(BooleanLiteral { value: self }))
2389    }
2390}
2391
2392// ---------------------------------------------------------------------------
2393// Helpers
2394// ---------------------------------------------------------------------------
2395
2396fn binary_op(left: Expression, right: Expression) -> BinaryOp {
2397    BinaryOp {
2398        left,
2399        right,
2400        left_comments: Vec::new(),
2401        operator_comments: Vec::new(),
2402        trailing_comments: Vec::new(),
2403        inferred_type: None,
2404    }
2405}
2406
2407// ---------------------------------------------------------------------------
2408// MergeBuilder
2409// ---------------------------------------------------------------------------
2410
2411/// Start building a `MERGE INTO` statement targeting the given table.
2412///
2413/// Returns a [`MergeBuilder`] which supports `.using()`, `.when_matched_update()`,
2414/// `.when_matched_delete()`, and `.when_not_matched_insert()`.
2415///
2416/// # Examples
2417///
2418/// ```
2419/// use polyglot_sql::builder::*;
2420///
2421/// let sql = merge_into("target")
2422///     .using("source", col("target.id").eq(col("source.id")))
2423///     .when_matched_update(vec![("name", col("source.name"))])
2424///     .when_not_matched_insert(&["id", "name"], vec![col("source.id"), col("source.name")])
2425///     .to_sql();
2426/// assert!(sql.contains("MERGE INTO"));
2427/// ```
2428pub fn merge_into(target: &str) -> MergeBuilder {
2429    MergeBuilder {
2430        target: Expression::Table(Box::new(builder_table_ref(target))),
2431        using: None,
2432        on: None,
2433        whens: Vec::new(),
2434    }
2435}
2436
2437/// Fluent builder for constructing `MERGE INTO` statements.
2438///
2439/// Created by the [`merge_into()`] entry-point function.
2440pub struct MergeBuilder {
2441    target: Expression,
2442    using: Option<Expression>,
2443    on: Option<Expression>,
2444    whens: Vec<Expression>,
2445}
2446
2447impl MergeBuilder {
2448    /// Set the source table and ON join condition.
2449    pub fn using(mut self, source: &str, on: Expr) -> Self {
2450        self.using = Some(Expression::Table(Box::new(builder_table_ref(source))));
2451        self.on = Some(on.0);
2452        self
2453    }
2454
2455    /// Add a `WHEN MATCHED THEN UPDATE SET` clause.
2456    pub fn when_matched_update(mut self, assignments: Vec<(&str, Expr)>) -> Self {
2457        let eqs: Vec<Expression> = assignments
2458            .into_iter()
2459            .map(|(col_name, val)| {
2460                Expression::Eq(Box::new(BinaryOp {
2461                    left: Expression::boxed_column(Column {
2462                        name: builder_identifier(col_name),
2463                        table: None,
2464                        join_mark: false,
2465                        trailing_comments: Vec::new(),
2466                        span: None,
2467                        inferred_type: None,
2468                    }),
2469                    right: val.0,
2470                    left_comments: Vec::new(),
2471                    operator_comments: Vec::new(),
2472                    trailing_comments: Vec::new(),
2473                    inferred_type: None,
2474                }))
2475            })
2476            .collect();
2477
2478        let action = Expression::Tuple(Box::new(Tuple {
2479            expressions: vec![
2480                Expression::Var(Box::new(Var {
2481                    this: "UPDATE".to_string(),
2482                })),
2483                Expression::Tuple(Box::new(Tuple { expressions: eqs })),
2484            ],
2485        }));
2486
2487        let when = Expression::When(Box::new(When {
2488            matched: Some(Box::new(Expression::Boolean(BooleanLiteral {
2489                value: true,
2490            }))),
2491            source: None,
2492            condition: None,
2493            then: Box::new(action),
2494        }));
2495        self.whens.push(when);
2496        self
2497    }
2498
2499    /// Add a `WHEN MATCHED THEN UPDATE SET` clause with an additional condition.
2500    pub fn when_matched_update_where(
2501        mut self,
2502        condition: Expr,
2503        assignments: Vec<(&str, Expr)>,
2504    ) -> Self {
2505        let eqs: Vec<Expression> = assignments
2506            .into_iter()
2507            .map(|(col_name, val)| {
2508                Expression::Eq(Box::new(BinaryOp {
2509                    left: Expression::boxed_column(Column {
2510                        name: builder_identifier(col_name),
2511                        table: None,
2512                        join_mark: false,
2513                        trailing_comments: Vec::new(),
2514                        span: None,
2515                        inferred_type: None,
2516                    }),
2517                    right: val.0,
2518                    left_comments: Vec::new(),
2519                    operator_comments: Vec::new(),
2520                    trailing_comments: Vec::new(),
2521                    inferred_type: None,
2522                }))
2523            })
2524            .collect();
2525
2526        let action = Expression::Tuple(Box::new(Tuple {
2527            expressions: vec![
2528                Expression::Var(Box::new(Var {
2529                    this: "UPDATE".to_string(),
2530                })),
2531                Expression::Tuple(Box::new(Tuple { expressions: eqs })),
2532            ],
2533        }));
2534
2535        let when = Expression::When(Box::new(When {
2536            matched: Some(Box::new(Expression::Boolean(BooleanLiteral {
2537                value: true,
2538            }))),
2539            source: None,
2540            condition: Some(Box::new(condition.0)),
2541            then: Box::new(action),
2542        }));
2543        self.whens.push(when);
2544        self
2545    }
2546
2547    /// Add a `WHEN MATCHED THEN DELETE` clause.
2548    pub fn when_matched_delete(mut self) -> Self {
2549        let action = Expression::Var(Box::new(Var {
2550            this: "DELETE".to_string(),
2551        }));
2552
2553        let when = Expression::When(Box::new(When {
2554            matched: Some(Box::new(Expression::Boolean(BooleanLiteral {
2555                value: true,
2556            }))),
2557            source: None,
2558            condition: None,
2559            then: Box::new(action),
2560        }));
2561        self.whens.push(when);
2562        self
2563    }
2564
2565    /// Add a `WHEN NOT MATCHED THEN INSERT (cols) VALUES (vals)` clause.
2566    pub fn when_not_matched_insert(mut self, columns: &[&str], values: Vec<Expr>) -> Self {
2567        let col_exprs: Vec<Expression> = columns
2568            .iter()
2569            .map(|c| {
2570                Expression::boxed_column(Column {
2571                    name: builder_identifier(c),
2572                    table: None,
2573                    join_mark: false,
2574                    trailing_comments: Vec::new(),
2575                    span: None,
2576                    inferred_type: None,
2577                })
2578            })
2579            .collect();
2580        let val_exprs: Vec<Expression> = values.into_iter().map(|v| v.0).collect();
2581
2582        let action = Expression::Tuple(Box::new(Tuple {
2583            expressions: vec![
2584                Expression::Var(Box::new(Var {
2585                    this: "INSERT".to_string(),
2586                })),
2587                Expression::Tuple(Box::new(Tuple {
2588                    expressions: col_exprs,
2589                })),
2590                Expression::Tuple(Box::new(Tuple {
2591                    expressions: val_exprs,
2592                })),
2593            ],
2594        }));
2595
2596        let when = Expression::When(Box::new(When {
2597            matched: Some(Box::new(Expression::Boolean(BooleanLiteral {
2598                value: false,
2599            }))),
2600            source: None,
2601            condition: None,
2602            then: Box::new(action),
2603        }));
2604        self.whens.push(when);
2605        self
2606    }
2607
2608    /// Consume this builder and produce the final [`Expression::Merge`] AST node.
2609    pub fn build(self) -> Expression {
2610        let whens_expr = Expression::Whens(Box::new(Whens {
2611            expressions: self.whens,
2612        }));
2613
2614        Expression::Merge(Box::new(Merge {
2615            this: Box::new(self.target),
2616            using: Box::new(
2617                self.using
2618                    .unwrap_or(Expression::Null(crate::expressions::Null)),
2619            ),
2620            on: self.on.map(Box::new),
2621            using_cond: None,
2622            whens: Some(Box::new(whens_expr)),
2623            with_: None,
2624            returning: None,
2625        }))
2626    }
2627
2628    /// Consume this builder, generate, and return the SQL string.
2629    pub fn to_sql(self) -> String {
2630        Generator::sql(&self.build()).unwrap_or_default()
2631    }
2632}
2633
2634fn parse_simple_data_type(name: &str) -> DataType {
2635    let upper = name.trim().to_uppercase();
2636    match upper.as_str() {
2637        "INT" | "INTEGER" => DataType::Int {
2638            length: None,
2639            integer_spelling: upper == "INTEGER",
2640        },
2641        "BIGINT" => DataType::BigInt { length: None },
2642        "SMALLINT" => DataType::SmallInt { length: None },
2643        "TINYINT" => DataType::TinyInt { length: None },
2644        "FLOAT" => DataType::Float {
2645            precision: None,
2646            scale: None,
2647            real_spelling: false,
2648        },
2649        "DOUBLE" => DataType::Double {
2650            precision: None,
2651            scale: None,
2652        },
2653        "BOOLEAN" | "BOOL" => DataType::Boolean,
2654        "TEXT" => DataType::Text,
2655        "DATE" => DataType::Date,
2656        "TIMESTAMP" => DataType::Timestamp {
2657            precision: None,
2658            timezone: false,
2659        },
2660        "VARCHAR" => DataType::VarChar {
2661            length: None,
2662            parenthesized_length: false,
2663        },
2664        "CHAR" => DataType::Char { length: None },
2665        _ => {
2666            // Try to parse as a full type via the parser for complex types
2667            if let Ok(ast) =
2668                crate::parser::Parser::parse_sql(&format!("SELECT CAST(x AS {})", name))
2669            {
2670                if let Expression::Select(s) = &ast[0] {
2671                    if let Some(Expression::Cast(c)) = s.expressions.first() {
2672                        return c.to.clone();
2673                    }
2674                }
2675            }
2676            // Fallback: treat as a custom type
2677            DataType::Custom {
2678                name: name.to_string(),
2679            }
2680        }
2681    }
2682}
2683
2684#[cfg(test)]
2685mod tests {
2686    use super::*;
2687
2688    #[test]
2689    fn test_simple_select() {
2690        let sql = select(["id", "name"]).from("users").to_sql();
2691        assert_eq!(sql, "SELECT id, name FROM users");
2692    }
2693
2694    #[test]
2695    fn test_builder_quotes_unsafe_identifier_tokens() {
2696        let sql = select(["Name; DROP TABLE titanic"]).to_sql();
2697        assert_eq!(sql, r#"SELECT "Name; DROP TABLE titanic""#);
2698    }
2699
2700    #[test]
2701    fn test_builder_string_literal_requires_lit() {
2702        let sql = select([lit("Name; DROP TABLE titanic")]).to_sql();
2703        assert_eq!(sql, "SELECT 'Name; DROP TABLE titanic'");
2704    }
2705
2706    #[test]
2707    fn test_builder_quotes_unsafe_table_name_tokens() {
2708        let sql = select(["id"]).from("users; DROP TABLE x").to_sql();
2709        assert_eq!(sql, r#"SELECT id FROM "users; DROP TABLE x""#);
2710    }
2711
2712    #[test]
2713    fn test_select_star() {
2714        let sql = select([star()]).from("users").to_sql();
2715        assert_eq!(sql, "SELECT * FROM users");
2716    }
2717
2718    #[test]
2719    fn test_select_with_where() {
2720        let sql = select(["id", "name"])
2721            .from("users")
2722            .where_(col("age").gt(lit(18)))
2723            .to_sql();
2724        assert_eq!(sql, "SELECT id, name FROM users WHERE age > 18");
2725    }
2726
2727    #[test]
2728    fn test_select_with_join() {
2729        let sql = select(["u.id", "o.amount"])
2730            .from("users")
2731            .join("orders", col("u.id").eq(col("o.user_id")))
2732            .to_sql();
2733        assert_eq!(
2734            sql,
2735            "SELECT u.id, o.amount FROM users JOIN orders ON u.id = o.user_id"
2736        );
2737    }
2738
2739    #[test]
2740    fn test_select_with_group_by_having() {
2741        let sql = select([col("dept"), func("COUNT", [star()]).alias("cnt")])
2742            .from("employees")
2743            .group_by(["dept"])
2744            .having(func("COUNT", [star()]).gt(lit(5)))
2745            .to_sql();
2746        assert_eq!(
2747            sql,
2748            "SELECT dept, COUNT(*) AS cnt FROM employees GROUP BY dept HAVING COUNT(*) > 5"
2749        );
2750    }
2751
2752    #[test]
2753    fn test_select_with_order_limit_offset() {
2754        let sql = select(["id", "name"])
2755            .from("users")
2756            .order_by(["name"])
2757            .limit(10)
2758            .offset(20)
2759            .to_sql();
2760        assert_eq!(
2761            sql,
2762            "SELECT id, name FROM users ORDER BY name LIMIT 10 OFFSET 20"
2763        );
2764    }
2765
2766    #[test]
2767    fn test_select_distinct() {
2768        let sql = select(["name"]).from("users").distinct().to_sql();
2769        assert_eq!(sql, "SELECT DISTINCT name FROM users");
2770    }
2771
2772    #[test]
2773    fn test_insert_values() {
2774        let sql = insert_into("users")
2775            .columns(["id", "name"])
2776            .values([lit(1), lit("Alice")])
2777            .values([lit(2), lit("Bob")])
2778            .to_sql();
2779        assert_eq!(
2780            sql,
2781            "INSERT INTO users (id, name) VALUES (1, 'Alice'), (2, 'Bob')"
2782        );
2783    }
2784
2785    #[test]
2786    fn test_insert_select() {
2787        let sql = insert_into("archive")
2788            .columns(["id", "name"])
2789            .query(select(["id", "name"]).from("users"))
2790            .to_sql();
2791        assert_eq!(
2792            sql,
2793            "INSERT INTO archive (id, name) SELECT id, name FROM users"
2794        );
2795    }
2796
2797    #[test]
2798    fn test_update() {
2799        let sql = update("users")
2800            .set("name", lit("Bob"))
2801            .set("age", lit(30))
2802            .where_(col("id").eq(lit(1)))
2803            .to_sql();
2804        assert_eq!(sql, "UPDATE users SET name = 'Bob', age = 30 WHERE id = 1");
2805    }
2806
2807    #[test]
2808    fn test_delete() {
2809        let sql = delete("users").where_(col("id").eq(lit(1))).to_sql();
2810        assert_eq!(sql, "DELETE FROM users WHERE id = 1");
2811    }
2812
2813    #[test]
2814    fn test_complex_where() {
2815        let sql = select(["id"])
2816            .from("users")
2817            .where_(
2818                col("age")
2819                    .gte(lit(18))
2820                    .and(col("active").eq(boolean(true)))
2821                    .and(col("name").like(lit("%test%"))),
2822            )
2823            .to_sql();
2824        assert_eq!(
2825            sql,
2826            "SELECT id FROM users WHERE age >= 18 AND active = TRUE AND name LIKE '%test%'"
2827        );
2828    }
2829
2830    #[test]
2831    fn test_in_list() {
2832        let sql = select(["id"])
2833            .from("users")
2834            .where_(col("status").in_list([lit("active"), lit("pending")]))
2835            .to_sql();
2836        assert_eq!(
2837            sql,
2838            "SELECT id FROM users WHERE status IN ('active', 'pending')"
2839        );
2840    }
2841
2842    #[test]
2843    fn test_between() {
2844        let sql = select(["id"])
2845            .from("orders")
2846            .where_(col("amount").between(lit(100), lit(500)))
2847            .to_sql();
2848        assert_eq!(
2849            sql,
2850            "SELECT id FROM orders WHERE amount BETWEEN 100 AND 500"
2851        );
2852    }
2853
2854    #[test]
2855    fn test_is_null() {
2856        let sql = select(["id"])
2857            .from("users")
2858            .where_(col("email").is_null())
2859            .to_sql();
2860        assert_eq!(sql, "SELECT id FROM users WHERE email IS NULL");
2861    }
2862
2863    #[test]
2864    fn test_arithmetic() {
2865        let sql = select([col("price").mul(col("quantity")).alias("total")])
2866            .from("items")
2867            .to_sql();
2868        assert_eq!(sql, "SELECT price * quantity AS total FROM items");
2869    }
2870
2871    #[test]
2872    fn test_cast() {
2873        let sql = select([col("id").cast("VARCHAR")]).from("users").to_sql();
2874        assert_eq!(sql, "SELECT CAST(id AS VARCHAR) FROM users");
2875    }
2876
2877    #[test]
2878    fn test_from_starter() {
2879        let sql = from("users").select_cols(["id", "name"]).to_sql();
2880        assert_eq!(sql, "SELECT id, name FROM users");
2881    }
2882
2883    #[test]
2884    fn test_qualified_column() {
2885        let sql = select([col("u.id"), col("u.name")]).from("users").to_sql();
2886        assert_eq!(sql, "SELECT u.id, u.name FROM users");
2887    }
2888
2889    #[test]
2890    fn test_not_condition() {
2891        let sql = select(["id"])
2892            .from("users")
2893            .where_(not(col("active").eq(boolean(true))))
2894            .to_sql();
2895        assert_eq!(sql, "SELECT id FROM users WHERE NOT active = TRUE");
2896    }
2897
2898    #[test]
2899    fn test_order_by_desc() {
2900        let sql = select(["id", "name"])
2901            .from("users")
2902            .order_by([col("name").desc()])
2903            .to_sql();
2904        assert_eq!(sql, "SELECT id, name FROM users ORDER BY name DESC");
2905    }
2906
2907    #[test]
2908    fn test_left_join() {
2909        let sql = select(["u.id", "o.amount"])
2910            .from("users")
2911            .left_join("orders", col("u.id").eq(col("o.user_id")))
2912            .to_sql();
2913        assert_eq!(
2914            sql,
2915            "SELECT u.id, o.amount FROM users LEFT JOIN orders ON u.id = o.user_id"
2916        );
2917    }
2918
2919    #[test]
2920    fn test_build_returns_expression() {
2921        let expr = select(["id"]).from("users").build();
2922        assert!(matches!(expr, Expression::Select(_)));
2923    }
2924
2925    #[test]
2926    fn test_expr_interop() {
2927        // Can use Expr in select list
2928        let age_check = col("age").gt(lit(18));
2929        let sql = select([col("id"), age_check.alias("is_adult")])
2930            .from("users")
2931            .to_sql();
2932        assert_eq!(sql, "SELECT id, age > 18 AS is_adult FROM users");
2933    }
2934
2935    // -- Step 2: sql_expr / condition tests --
2936
2937    #[test]
2938    fn test_sql_expr_simple() {
2939        let expr = sql_expr("age > 18");
2940        let sql = select(["id"]).from("users").where_(expr).to_sql();
2941        assert_eq!(sql, "SELECT id FROM users WHERE age > 18");
2942    }
2943
2944    #[test]
2945    fn test_sql_expr_compound() {
2946        let expr = sql_expr("a > 1 AND b < 10");
2947        let sql = select(["*"]).from("t").where_(expr).to_sql();
2948        assert_eq!(sql, "SELECT * FROM t WHERE a > 1 AND b < 10");
2949    }
2950
2951    #[test]
2952    fn test_sql_expr_function() {
2953        let expr = sql_expr("COALESCE(a, b, 0)");
2954        let sql = select([expr.alias("val")]).from("t").to_sql();
2955        assert_eq!(sql, "SELECT COALESCE(a, b, 0) AS val FROM t");
2956    }
2957
2958    #[test]
2959    fn test_condition_alias() {
2960        let cond = condition("x > 0");
2961        let sql = select(["*"]).from("t").where_(cond).to_sql();
2962        assert_eq!(sql, "SELECT * FROM t WHERE x > 0");
2963    }
2964
2965    // -- Step 3: ilike, rlike, not_in tests --
2966
2967    #[test]
2968    fn test_ilike() {
2969        let sql = select(["id"])
2970            .from("users")
2971            .where_(col("name").ilike(lit("%test%")))
2972            .to_sql();
2973        assert_eq!(sql, "SELECT id FROM users WHERE name ILIKE '%test%'");
2974    }
2975
2976    #[test]
2977    fn test_rlike() {
2978        let sql = select(["id"])
2979            .from("users")
2980            .where_(col("name").rlike(lit("^[A-Z]")))
2981            .to_sql();
2982        assert_eq!(
2983            sql,
2984            "SELECT id FROM users WHERE REGEXP_LIKE(name, '^[A-Z]')"
2985        );
2986    }
2987
2988    #[test]
2989    fn test_not_in() {
2990        let sql = select(["id"])
2991            .from("users")
2992            .where_(col("status").not_in([lit("deleted"), lit("banned")]))
2993            .to_sql();
2994        assert_eq!(
2995            sql,
2996            "SELECT id FROM users WHERE NOT status IN ('deleted', 'banned')"
2997        );
2998    }
2999
3000    // -- Step 4: CaseBuilder tests --
3001
3002    #[test]
3003    fn test_case_searched() {
3004        let expr = case()
3005            .when(col("x").gt(lit(0)), lit("positive"))
3006            .when(col("x").eq(lit(0)), lit("zero"))
3007            .else_(lit("negative"))
3008            .build();
3009        let sql = select([expr.alias("label")]).from("t").to_sql();
3010        assert_eq!(
3011            sql,
3012            "SELECT CASE WHEN x > 0 THEN 'positive' WHEN x = 0 THEN 'zero' ELSE 'negative' END AS label FROM t"
3013        );
3014    }
3015
3016    #[test]
3017    fn test_case_simple() {
3018        let expr = case_of(col("status"))
3019            .when(lit(1), lit("active"))
3020            .when(lit(0), lit("inactive"))
3021            .build();
3022        let sql = select([expr.alias("status_label")]).from("t").to_sql();
3023        assert_eq!(
3024            sql,
3025            "SELECT CASE status WHEN 1 THEN 'active' WHEN 0 THEN 'inactive' END AS status_label FROM t"
3026        );
3027    }
3028
3029    #[test]
3030    fn test_case_no_else() {
3031        let expr = case().when(col("x").gt(lit(0)), lit("yes")).build();
3032        let sql = select([expr]).from("t").to_sql();
3033        assert_eq!(sql, "SELECT CASE WHEN x > 0 THEN 'yes' END FROM t");
3034    }
3035
3036    // -- Step 5: subquery tests --
3037
3038    #[test]
3039    fn test_subquery_in_from() {
3040        let inner = select(["id", "name"])
3041            .from("users")
3042            .where_(col("active").eq(boolean(true)));
3043        let outer = select(["sub.id"])
3044            .from_expr(subquery(inner, "sub"))
3045            .to_sql();
3046        assert_eq!(
3047            outer,
3048            "SELECT sub.id FROM (SELECT id, name FROM users WHERE active = TRUE) AS sub"
3049        );
3050    }
3051
3052    #[test]
3053    fn test_subquery_in_join() {
3054        let inner = select([col("user_id"), func("SUM", [col("amount")]).alias("total")])
3055            .from("orders")
3056            .group_by(["user_id"]);
3057        let sql = select(["u.name", "o.total"])
3058            .from("users")
3059            .join("orders", col("u.id").eq(col("o.user_id")))
3060            .to_sql();
3061        assert!(sql.contains("JOIN"));
3062        // Just verify the subquery builder doesn't panic
3063        let _sub = subquery(inner, "o");
3064    }
3065
3066    // -- Step 6: SetOpBuilder tests --
3067
3068    #[test]
3069    fn test_union() {
3070        let sql = union(select(["id"]).from("a"), select(["id"]).from("b")).to_sql();
3071        assert_eq!(sql, "SELECT id FROM a UNION SELECT id FROM b");
3072    }
3073
3074    #[test]
3075    fn test_union_all() {
3076        let sql = union_all(select(["id"]).from("a"), select(["id"]).from("b")).to_sql();
3077        assert_eq!(sql, "SELECT id FROM a UNION ALL SELECT id FROM b");
3078    }
3079
3080    #[test]
3081    fn test_intersect_builder() {
3082        let sql = intersect(select(["id"]).from("a"), select(["id"]).from("b")).to_sql();
3083        assert_eq!(sql, "SELECT id FROM a INTERSECT SELECT id FROM b");
3084    }
3085
3086    #[test]
3087    fn test_except_builder() {
3088        let sql = except_(select(["id"]).from("a"), select(["id"]).from("b")).to_sql();
3089        assert_eq!(sql, "SELECT id FROM a EXCEPT SELECT id FROM b");
3090    }
3091
3092    #[test]
3093    fn test_union_with_order_limit() {
3094        let sql = union(select(["id"]).from("a"), select(["id"]).from("b"))
3095            .order_by(["id"])
3096            .limit(10)
3097            .to_sql();
3098        assert!(sql.contains("UNION"));
3099        assert!(sql.contains("ORDER BY"));
3100        assert!(sql.contains("LIMIT"));
3101    }
3102
3103    #[test]
3104    fn test_select_builder_union() {
3105        let sql = select(["id"])
3106            .from("a")
3107            .union(select(["id"]).from("b"))
3108            .to_sql();
3109        assert_eq!(sql, "SELECT id FROM a UNION SELECT id FROM b");
3110    }
3111
3112    // -- Step 7: SelectBuilder extensions tests --
3113
3114    #[test]
3115    fn test_qualify() {
3116        let sql = select(["id", "name"])
3117            .from("users")
3118            .qualify(col("rn").eq(lit(1)))
3119            .to_sql();
3120        assert_eq!(sql, "SELECT id, name FROM users QUALIFY rn = 1");
3121    }
3122
3123    #[test]
3124    fn test_right_join() {
3125        let sql = select(["u.id", "o.amount"])
3126            .from("users")
3127            .right_join("orders", col("u.id").eq(col("o.user_id")))
3128            .to_sql();
3129        assert_eq!(
3130            sql,
3131            "SELECT u.id, o.amount FROM users RIGHT JOIN orders ON u.id = o.user_id"
3132        );
3133    }
3134
3135    #[test]
3136    fn test_cross_join() {
3137        let sql = select(["a.x", "b.y"]).from("a").cross_join("b").to_sql();
3138        assert_eq!(sql, "SELECT a.x, b.y FROM a CROSS JOIN b");
3139    }
3140
3141    #[test]
3142    fn test_lateral_view() {
3143        let sql = select(["id", "col_val"])
3144            .from("t")
3145            .lateral_view(func("EXPLODE", [col("arr")]), "lv", ["col_val"])
3146            .to_sql();
3147        assert!(sql.contains("LATERAL VIEW"));
3148        assert!(sql.contains("EXPLODE"));
3149    }
3150
3151    #[test]
3152    fn test_window_clause() {
3153        let sql = select(["id"])
3154            .from("t")
3155            .window(
3156                "w",
3157                WindowDefBuilder::new()
3158                    .partition_by(["dept"])
3159                    .order_by(["salary"]),
3160            )
3161            .to_sql();
3162        assert!(sql.contains("WINDOW"));
3163        assert!(sql.contains("PARTITION BY"));
3164    }
3165
3166    // -- XOR operator tests --
3167
3168    #[test]
3169    fn test_xor() {
3170        let sql = select(["*"])
3171            .from("t")
3172            .where_(col("a").xor(col("b")))
3173            .to_sql();
3174        assert_eq!(sql, "SELECT * FROM t WHERE a XOR b");
3175    }
3176
3177    // -- FOR UPDATE / FOR SHARE tests --
3178
3179    #[test]
3180    fn test_for_update() {
3181        let sql = select(["id"]).from("t").for_update().to_sql();
3182        assert_eq!(sql, "SELECT id FROM t FOR UPDATE");
3183    }
3184
3185    #[test]
3186    fn test_for_share() {
3187        let sql = select(["id"]).from("t").for_share().to_sql();
3188        assert_eq!(sql, "SELECT id FROM t FOR SHARE");
3189    }
3190
3191    // -- Hint tests --
3192
3193    #[test]
3194    fn test_hint() {
3195        let sql = select(["*"]).from("t").hint("FULL(t)").to_sql();
3196        assert!(sql.contains("FULL(t)"), "Expected hint in: {}", sql);
3197    }
3198
3199    // -- CTAS tests --
3200
3201    #[test]
3202    fn test_ctas() {
3203        let expr = select(["*"]).from("t").ctas("new_table");
3204        let sql = Generator::sql(&expr).unwrap();
3205        assert_eq!(sql, "CREATE TABLE new_table AS SELECT * FROM t");
3206    }
3207
3208    // -- MergeBuilder tests --
3209
3210    #[test]
3211    fn test_merge_update_insert() {
3212        let sql = merge_into("target")
3213            .using("source", col("target.id").eq(col("source.id")))
3214            .when_matched_update(vec![("name", col("source.name"))])
3215            .when_not_matched_insert(&["id", "name"], vec![col("source.id"), col("source.name")])
3216            .to_sql();
3217        assert!(
3218            sql.contains("MERGE INTO"),
3219            "Expected MERGE INTO in: {}",
3220            sql
3221        );
3222        assert!(sql.contains("USING"), "Expected USING in: {}", sql);
3223        assert!(
3224            sql.contains("WHEN MATCHED"),
3225            "Expected WHEN MATCHED in: {}",
3226            sql
3227        );
3228        assert!(
3229            sql.contains("UPDATE SET"),
3230            "Expected UPDATE SET in: {}",
3231            sql
3232        );
3233        assert!(
3234            sql.contains("WHEN NOT MATCHED"),
3235            "Expected WHEN NOT MATCHED in: {}",
3236            sql
3237        );
3238        assert!(sql.contains("INSERT"), "Expected INSERT in: {}", sql);
3239    }
3240
3241    #[test]
3242    fn test_merge_delete() {
3243        let sql = merge_into("target")
3244            .using("source", col("target.id").eq(col("source.id")))
3245            .when_matched_delete()
3246            .to_sql();
3247        assert!(
3248            sql.contains("MERGE INTO"),
3249            "Expected MERGE INTO in: {}",
3250            sql
3251        );
3252        assert!(
3253            sql.contains("WHEN MATCHED THEN DELETE"),
3254            "Expected WHEN MATCHED THEN DELETE in: {}",
3255            sql
3256        );
3257    }
3258
3259    #[test]
3260    fn test_merge_with_condition() {
3261        let sql = merge_into("target")
3262            .using("source", col("target.id").eq(col("source.id")))
3263            .when_matched_update_where(
3264                col("source.active").eq(boolean(true)),
3265                vec![("name", col("source.name"))],
3266            )
3267            .to_sql();
3268        assert!(
3269            sql.contains("MERGE INTO"),
3270            "Expected MERGE INTO in: {}",
3271            sql
3272        );
3273        assert!(
3274            sql.contains("AND source.active = TRUE"),
3275            "Expected condition in: {}",
3276            sql
3277        );
3278    }
3279}