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            on_cluster: None,
807            alias: None,
808            alias_explicit_as: false,
809            using: Vec::new(),
810            where_clause: None,
811            output: None,
812            leading_comments: Vec::new(),
813            with: None,
814            limit: None,
815            order_by: None,
816            returning: Vec::new(),
817            tables: Vec::new(),
818            tables_from_using: false,
819            joins: Vec::new(),
820            force_index: None,
821            no_from: false,
822        },
823    }
824}
825
826/// Start building an `INSERT INTO` statement targeting the given table.
827///
828/// Returns an [`InsertBuilder`] which supports `.columns()`, `.values()`, and
829/// `.query()` for INSERT ... SELECT.
830///
831/// # Examples
832///
833/// ```
834/// use polyglot_sql::builder::*;
835///
836/// let sql = insert_into("users")
837///     .columns(["id", "name"])
838///     .values([lit(1), lit("Alice")])
839///     .to_sql();
840/// assert_eq!(sql, "INSERT INTO users (id, name) VALUES (1, 'Alice')");
841/// ```
842pub fn insert_into(table_name: &str) -> InsertBuilder {
843    InsertBuilder {
844        insert: Insert {
845            table: builder_table_ref(table_name),
846            columns: Vec::new(),
847            values: Vec::new(),
848            query: None,
849            overwrite: false,
850            partition: Vec::new(),
851            directory: None,
852            returning: Vec::new(),
853            output: None,
854            on_conflict: None,
855            leading_comments: Vec::new(),
856            if_exists: false,
857            with: None,
858            ignore: false,
859            source_alias: None,
860            alias: None,
861            alias_explicit_as: false,
862            default_values: false,
863            by_name: false,
864            conflict_action: None,
865            is_replace: false,
866            hint: None,
867            replace_where: None,
868            source: None,
869            function_target: None,
870            partition_by: None,
871            settings: Vec::new(),
872        },
873    }
874}
875
876/// Start building an `UPDATE` statement targeting the given table.
877///
878/// Returns an [`UpdateBuilder`] which supports `.set()` for column assignments,
879/// `.where_()` for predicates, and `.from()` for PostgreSQL/Snowflake-style
880/// UPDATE ... FROM syntax.
881///
882/// # Examples
883///
884/// ```
885/// use polyglot_sql::builder::*;
886///
887/// let sql = update("users")
888///     .set("name", lit("Bob"))
889///     .where_(col("id").eq(lit(1)))
890///     .to_sql();
891/// assert_eq!(sql, "UPDATE users SET name = 'Bob' WHERE id = 1");
892/// ```
893pub fn update(table_name: &str) -> UpdateBuilder {
894    UpdateBuilder {
895        update: Update {
896            table: builder_table_ref(table_name),
897            extra_tables: Vec::new(),
898            table_joins: Vec::new(),
899            set: Vec::new(),
900            from_clause: None,
901            from_joins: Vec::new(),
902            where_clause: None,
903            returning: Vec::new(),
904            output: None,
905            with: None,
906            leading_comments: Vec::new(),
907            limit: None,
908            order_by: None,
909            from_before_set: false,
910        },
911    }
912}
913
914// ---------------------------------------------------------------------------
915// Expr wrapper (for operator methods)
916// ---------------------------------------------------------------------------
917
918/// A thin wrapper around [`Expression`] that provides fluent operator methods.
919///
920/// `Expr` is the primary value type flowing through the builder API. It wraps a single
921/// AST [`Expression`] node and adds convenience methods for comparisons (`.eq()`,
922/// `.gt()`, etc.), logical connectives (`.and()`, `.or()`, `.not()`), arithmetic
923/// (`.add()`, `.sub()`, `.mul()`, `.div()`), pattern matching (`.like()`, `.ilike()`,
924/// `.rlike()`), and other SQL operations (`.in_list()`, `.between()`, `.is_null()`,
925/// `.alias()`, `.cast()`, `.asc()`, `.desc()`).
926///
927/// The inner [`Expression`] is publicly accessible via the `.0` field or
928/// [`Expr::into_inner()`].
929///
930/// # Examples
931///
932/// ```
933/// use polyglot_sql::builder::*;
934///
935/// let condition = col("age").gte(lit(18)).and(col("active").eq(boolean(true)));
936/// assert_eq!(condition.to_sql(), "age >= 18 AND active = TRUE");
937/// ```
938#[derive(Debug, Clone)]
939pub struct Expr(pub Expression);
940
941impl Expr {
942    /// Consume this wrapper and return the inner [`Expression`] node.
943    pub fn into_inner(self) -> Expression {
944        self.0
945    }
946
947    /// Generate a SQL string from this expression using the default (generic) dialect.
948    ///
949    /// Returns an empty string if generation fails.
950    pub fn to_sql(&self) -> String {
951        Generator::sql(&self.0).unwrap_or_default()
952    }
953
954    // -- Comparison operators --
955
956    /// Produce a `self = other` equality comparison.
957    pub fn eq(self, other: Expr) -> Expr {
958        Expr(Expression::Eq(Box::new(binary_op(self.0, other.0))))
959    }
960
961    /// Produce a `self <> other` inequality comparison.
962    pub fn neq(self, other: Expr) -> Expr {
963        Expr(Expression::Neq(Box::new(binary_op(self.0, other.0))))
964    }
965
966    /// Produce a `self < other` less-than comparison.
967    pub fn lt(self, other: Expr) -> Expr {
968        Expr(Expression::Lt(Box::new(binary_op(self.0, other.0))))
969    }
970
971    /// Produce a `self <= other` less-than-or-equal comparison.
972    pub fn lte(self, other: Expr) -> Expr {
973        Expr(Expression::Lte(Box::new(binary_op(self.0, other.0))))
974    }
975
976    /// Produce a `self > other` greater-than comparison.
977    pub fn gt(self, other: Expr) -> Expr {
978        Expr(Expression::Gt(Box::new(binary_op(self.0, other.0))))
979    }
980
981    /// Produce a `self >= other` greater-than-or-equal comparison.
982    pub fn gte(self, other: Expr) -> Expr {
983        Expr(Expression::Gte(Box::new(binary_op(self.0, other.0))))
984    }
985
986    // -- Logical operators --
987
988    /// Produce a `self AND other` logical conjunction.
989    pub fn and(self, other: Expr) -> Expr {
990        Expr(Expression::And(Box::new(binary_op(self.0, other.0))))
991    }
992
993    /// Produce a `self OR other` logical disjunction.
994    pub fn or(self, other: Expr) -> Expr {
995        Expr(Expression::Or(Box::new(binary_op(self.0, other.0))))
996    }
997
998    /// Produce a `NOT self` logical negation.
999    pub fn not(self) -> Expr {
1000        Expr(Expression::Not(Box::new(UnaryOp::new(self.0))))
1001    }
1002
1003    /// Produce a `self XOR other` logical exclusive-or.
1004    pub fn xor(self, other: Expr) -> Expr {
1005        Expr(Expression::Xor(Box::new(Xor {
1006            this: Some(Box::new(self.0)),
1007            expression: Some(Box::new(other.0)),
1008            expressions: vec![],
1009        })))
1010    }
1011
1012    // -- Arithmetic operators --
1013
1014    /// Produce a `self + other` addition expression.
1015    pub fn add(self, other: Expr) -> Expr {
1016        Expr(Expression::Add(Box::new(binary_op(self.0, other.0))))
1017    }
1018
1019    /// Produce a `self - other` subtraction expression.
1020    pub fn sub(self, other: Expr) -> Expr {
1021        Expr(Expression::Sub(Box::new(binary_op(self.0, other.0))))
1022    }
1023
1024    /// Produce a `self * other` multiplication expression.
1025    pub fn mul(self, other: Expr) -> Expr {
1026        Expr(Expression::Mul(Box::new(binary_op(self.0, other.0))))
1027    }
1028
1029    /// Produce a `self / other` division expression.
1030    pub fn div(self, other: Expr) -> Expr {
1031        Expr(Expression::Div(Box::new(binary_op(self.0, other.0))))
1032    }
1033
1034    // -- Other operators --
1035
1036    /// Produce a `self IS NULL` predicate.
1037    pub fn is_null(self) -> Expr {
1038        Expr(Expression::Is(Box::new(BinaryOp {
1039            left: self.0,
1040            right: Expression::Null(Null),
1041            left_comments: Vec::new(),
1042            operator_comments: Vec::new(),
1043            trailing_comments: Vec::new(),
1044            inferred_type: None,
1045        })))
1046    }
1047
1048    /// Produce a `self IS NOT NULL` predicate (implemented as `NOT (self IS NULL)`).
1049    pub fn is_not_null(self) -> Expr {
1050        Expr(Expression::Not(Box::new(UnaryOp::new(Expression::Is(
1051            Box::new(BinaryOp {
1052                left: self.0,
1053                right: Expression::Null(Null),
1054                left_comments: Vec::new(),
1055                operator_comments: Vec::new(),
1056                trailing_comments: Vec::new(),
1057                inferred_type: None,
1058            }),
1059        )))))
1060    }
1061
1062    /// Produce a `self IN (values...)` membership test.
1063    ///
1064    /// Each element of `values` becomes an item in the parenthesized list.
1065    pub fn in_list(self, values: impl IntoIterator<Item = Expr>) -> Expr {
1066        Expr(Expression::In(Box::new(In {
1067            this: self.0,
1068            expressions: values.into_iter().map(|v| v.0).collect(),
1069            query: None,
1070            not: false,
1071            global: false,
1072            unnest: None,
1073            is_field: false,
1074        })))
1075    }
1076
1077    /// Produce a `self BETWEEN low AND high` range test.
1078    pub fn between(self, low: Expr, high: Expr) -> Expr {
1079        Expr(Expression::Between(Box::new(Between {
1080            this: self.0,
1081            low: low.0,
1082            high: high.0,
1083            not: false,
1084            symmetric: None,
1085        })))
1086    }
1087
1088    /// Produce a `self LIKE pattern` case-sensitive pattern match.
1089    pub fn like(self, pattern: Expr) -> Expr {
1090        Expr(Expression::Like(Box::new(LikeOp {
1091            left: self.0,
1092            right: pattern.0,
1093            escape: None,
1094            quantifier: None,
1095            inferred_type: None,
1096        })))
1097    }
1098
1099    /// Produce a `self AS alias` expression alias.
1100    pub fn alias(self, name: &str) -> Expr {
1101        alias(self, name)
1102    }
1103
1104    /// Produce a `CAST(self AS type)` type conversion.
1105    ///
1106    /// The `to` parameter is parsed as a data type name; see [`cast()`] for details.
1107    pub fn cast(self, to: &str) -> Expr {
1108        cast(self, to)
1109    }
1110
1111    /// Wrap this expression with ascending sort order (`self ASC`).
1112    ///
1113    /// Used in ORDER BY clauses. Expressions without an explicit `.asc()` or `.desc()`
1114    /// call default to ascending order when passed to [`SelectBuilder::order_by()`].
1115    pub fn asc(self) -> Expr {
1116        Expr(Expression::Ordered(Box::new(Ordered {
1117            this: self.0,
1118            desc: false,
1119            nulls_first: None,
1120            explicit_asc: true,
1121            with_fill: None,
1122        })))
1123    }
1124
1125    /// Wrap this expression with descending sort order (`self DESC`).
1126    ///
1127    /// Used in ORDER BY clauses.
1128    pub fn desc(self) -> Expr {
1129        Expr(Expression::Ordered(Box::new(Ordered {
1130            this: self.0,
1131            desc: true,
1132            nulls_first: None,
1133            explicit_asc: false,
1134            with_fill: None,
1135        })))
1136    }
1137
1138    /// Produce a `self ILIKE pattern` case-insensitive pattern match.
1139    ///
1140    /// Supported by PostgreSQL, Snowflake, and other dialects. Dialects that do not
1141    /// support `ILIKE` natively may need transpilation.
1142    pub fn ilike(self, pattern: Expr) -> Expr {
1143        Expr(Expression::ILike(Box::new(LikeOp {
1144            left: self.0,
1145            right: pattern.0,
1146            escape: None,
1147            quantifier: None,
1148            inferred_type: None,
1149        })))
1150    }
1151
1152    /// Produce a `REGEXP_LIKE(self, pattern)` regular expression match.
1153    ///
1154    /// The generated SQL uses the `REGEXP_LIKE` function form. Different dialects may
1155    /// render this as `RLIKE`, `REGEXP`, or `REGEXP_LIKE` after transpilation.
1156    pub fn rlike(self, pattern: Expr) -> Expr {
1157        Expr(Expression::RegexpLike(Box::new(RegexpFunc {
1158            this: self.0,
1159            pattern: pattern.0,
1160            flags: None,
1161        })))
1162    }
1163
1164    /// Produce a `self NOT IN (values...)` negated membership test.
1165    ///
1166    /// Each element of `values` becomes an item in the parenthesized list.
1167    pub fn not_in(self, values: impl IntoIterator<Item = Expr>) -> Expr {
1168        Expr(Expression::In(Box::new(In {
1169            this: self.0,
1170            expressions: values.into_iter().map(|v| v.0).collect(),
1171            query: None,
1172            not: true,
1173            global: false,
1174            unnest: None,
1175            is_field: false,
1176        })))
1177    }
1178}
1179
1180// ---------------------------------------------------------------------------
1181// SelectBuilder
1182// ---------------------------------------------------------------------------
1183
1184/// Fluent builder for constructing `SELECT` statements.
1185///
1186/// Created by the [`select()`] or [`from()`] entry-point functions. Methods on this
1187/// builder return `self` so they can be chained. Call [`.build()`](SelectBuilder::build)
1188/// to obtain an [`Expression`], or [`.to_sql()`](SelectBuilder::to_sql) to generate a
1189/// SQL string directly.
1190///
1191/// # Examples
1192///
1193/// ```
1194/// use polyglot_sql::builder::*;
1195///
1196/// let sql = select(["u.id", "u.name"])
1197///     .from("users")
1198///     .left_join("orders", col("u.id").eq(col("o.user_id")))
1199///     .where_(col("u.active").eq(boolean(true)))
1200///     .group_by(["u.id", "u.name"])
1201///     .order_by([col("u.name").asc()])
1202///     .limit(100)
1203///     .to_sql();
1204/// ```
1205pub struct SelectBuilder {
1206    select: Select,
1207}
1208
1209impl SelectBuilder {
1210    fn new() -> Self {
1211        SelectBuilder {
1212            select: Select::new(),
1213        }
1214    }
1215
1216    /// Append columns to the SELECT list.
1217    ///
1218    /// Accepts any iterable of [`IntoExpr`] items. This is primarily useful when the
1219    /// builder was created via [`from()`] and columns need to be added afterward.
1220    pub fn select_cols<I, E>(mut self, expressions: I) -> Self
1221    where
1222        I: IntoIterator<Item = E>,
1223        E: IntoExpr,
1224    {
1225        for expr in expressions {
1226            self.select.expressions.push(expr.into_expr().0);
1227        }
1228        self
1229    }
1230
1231    /// Set the FROM clause to reference the given table by name.
1232    pub fn from(mut self, table_name: &str) -> Self {
1233        self.select.from = Some(From {
1234            expressions: vec![Expression::Table(Box::new(builder_table_ref(table_name)))],
1235        });
1236        self
1237    }
1238
1239    /// Set the FROM clause to an arbitrary expression (e.g. a subquery or table function).
1240    ///
1241    /// Use this instead of [`SelectBuilder::from()`] when the source is not a simple
1242    /// table name -- for example, a [`subquery()`] or a table-valued function.
1243    pub fn from_expr(mut self, expr: Expr) -> Self {
1244        self.select.from = Some(From {
1245            expressions: vec![expr.0],
1246        });
1247        self
1248    }
1249
1250    /// Add an inner `JOIN` clause with the given ON condition.
1251    pub fn join(mut self, table_name: &str, on: Expr) -> Self {
1252        self.select.joins.push(Join {
1253            kind: JoinKind::Inner,
1254            this: Expression::Table(Box::new(builder_table_ref(table_name))),
1255            on: Some(on.0),
1256            using: Vec::new(),
1257            use_inner_keyword: false,
1258            use_outer_keyword: false,
1259            deferred_condition: false,
1260            join_hint: None,
1261            match_condition: None,
1262            pivots: Vec::new(),
1263            comments: Vec::new(),
1264            nesting_group: 0,
1265            directed: false,
1266        });
1267        self
1268    }
1269
1270    /// Add a `LEFT JOIN` clause with the given ON condition.
1271    pub fn left_join(mut self, table_name: &str, on: Expr) -> Self {
1272        self.select.joins.push(Join {
1273            kind: JoinKind::Left,
1274            this: Expression::Table(Box::new(builder_table_ref(table_name))),
1275            on: Some(on.0),
1276            using: Vec::new(),
1277            use_inner_keyword: false,
1278            use_outer_keyword: false,
1279            deferred_condition: false,
1280            join_hint: None,
1281            match_condition: None,
1282            pivots: Vec::new(),
1283            comments: Vec::new(),
1284            nesting_group: 0,
1285            directed: false,
1286        });
1287        self
1288    }
1289
1290    /// Set the WHERE clause to filter rows by the given condition.
1291    ///
1292    /// Calling this multiple times replaces the previous WHERE condition. To combine
1293    /// multiple predicates, chain them with [`.and()`](Expr::and) or [`.or()`](Expr::or)
1294    /// on a single [`Expr`].
1295    pub fn where_(mut self, condition: Expr) -> Self {
1296        self.select.where_clause = Some(Where { this: condition.0 });
1297        self
1298    }
1299
1300    /// Set the GROUP BY clause with the given grouping expressions.
1301    pub fn group_by<I, E>(mut self, expressions: I) -> Self
1302    where
1303        I: IntoIterator<Item = E>,
1304        E: IntoExpr,
1305    {
1306        self.select.group_by = Some(GroupBy {
1307            expressions: expressions.into_iter().map(|e| e.into_expr().0).collect(),
1308            all: None,
1309            totals: false,
1310            comments: Vec::new(),
1311        });
1312        self
1313    }
1314
1315    /// Set the HAVING clause to filter groups by the given condition.
1316    pub fn having(mut self, condition: Expr) -> Self {
1317        self.select.having = Some(Having {
1318            this: condition.0,
1319            comments: Vec::new(),
1320        });
1321        self
1322    }
1323
1324    /// Set the ORDER BY clause with the given sort expressions.
1325    ///
1326    /// Expressions that are not already wrapped with [`.asc()`](Expr::asc) or
1327    /// [`.desc()`](Expr::desc) default to ascending order. String values are
1328    /// interpreted as column names via [`IntoExpr`].
1329    pub fn order_by<I, E>(mut self, expressions: I) -> Self
1330    where
1331        I: IntoIterator<Item = E>,
1332        E: IntoExpr,
1333    {
1334        self.select.order_by = Some(OrderBy {
1335            siblings: false,
1336            comments: Vec::new(),
1337            expressions: expressions
1338                .into_iter()
1339                .map(|e| {
1340                    let expr = e.into_expr().0;
1341                    match expr {
1342                        Expression::Ordered(_) => expr,
1343                        other => Expression::Ordered(Box::new(Ordered {
1344                            this: other,
1345                            desc: false,
1346                            nulls_first: None,
1347                            explicit_asc: false,
1348                            with_fill: None,
1349                        })),
1350                    }
1351                })
1352                .collect::<Vec<_>>()
1353                .into_iter()
1354                .map(|e| {
1355                    if let Expression::Ordered(o) = e {
1356                        *o
1357                    } else {
1358                        Ordered {
1359                            this: e,
1360                            desc: false,
1361                            nulls_first: None,
1362                            explicit_asc: false,
1363                            with_fill: None,
1364                        }
1365                    }
1366                })
1367                .collect(),
1368        });
1369        self
1370    }
1371
1372    /// Set the SORT BY clause with the given sort expressions.
1373    ///
1374    /// SORT BY is used in Hive/Spark to sort data within each reducer (partition),
1375    /// as opposed to ORDER BY which sorts globally. Expressions that are not already
1376    /// wrapped with [`.asc()`](Expr::asc) or [`.desc()`](Expr::desc) default to
1377    /// ascending order.
1378    pub fn sort_by<I, E>(mut self, expressions: I) -> Self
1379    where
1380        I: IntoIterator<Item = E>,
1381        E: IntoExpr,
1382    {
1383        self.select.sort_by = Some(SortBy {
1384            expressions: expressions
1385                .into_iter()
1386                .map(|e| {
1387                    let expr = e.into_expr().0;
1388                    match expr {
1389                        Expression::Ordered(o) => *o,
1390                        other => Ordered {
1391                            this: other,
1392                            desc: false,
1393                            nulls_first: None,
1394                            explicit_asc: false,
1395                            with_fill: None,
1396                        },
1397                    }
1398                })
1399                .collect(),
1400        });
1401        self
1402    }
1403
1404    /// Set the LIMIT clause to restrict the result set to `count` rows.
1405    pub fn limit(mut self, count: usize) -> Self {
1406        self.select.limit = Some(Limit {
1407            this: Expression::Literal(Box::new(Literal::Number(count.to_string()))),
1408            percent: false,
1409            comments: Vec::new(),
1410        });
1411        self
1412    }
1413
1414    /// Set the OFFSET clause to skip the first `count` rows.
1415    pub fn offset(mut self, count: usize) -> Self {
1416        self.select.offset = Some(Offset {
1417            this: Expression::Literal(Box::new(Literal::Number(count.to_string()))),
1418            rows: None,
1419        });
1420        self
1421    }
1422
1423    /// Enable the DISTINCT modifier on the SELECT clause.
1424    pub fn distinct(mut self) -> Self {
1425        self.select.distinct = true;
1426        self
1427    }
1428
1429    /// Add a QUALIFY clause to filter rows after window function evaluation.
1430    ///
1431    /// QUALIFY is supported by Snowflake, BigQuery, DuckDB, and Databricks. It acts
1432    /// like a WHERE clause but is applied after window functions are computed.
1433    pub fn qualify(mut self, condition: Expr) -> Self {
1434        self.select.qualify = Some(Qualify { this: condition.0 });
1435        self
1436    }
1437
1438    /// Add a `RIGHT JOIN` clause with the given ON condition.
1439    pub fn right_join(mut self, table_name: &str, on: Expr) -> Self {
1440        self.select.joins.push(Join {
1441            kind: JoinKind::Right,
1442            this: Expression::Table(Box::new(builder_table_ref(table_name))),
1443            on: Some(on.0),
1444            using: Vec::new(),
1445            use_inner_keyword: false,
1446            use_outer_keyword: false,
1447            deferred_condition: false,
1448            join_hint: None,
1449            match_condition: None,
1450            pivots: Vec::new(),
1451            comments: Vec::new(),
1452            nesting_group: 0,
1453            directed: false,
1454        });
1455        self
1456    }
1457
1458    /// Add a `CROSS JOIN` clause (Cartesian product, no ON condition).
1459    pub fn cross_join(mut self, table_name: &str) -> Self {
1460        self.select.joins.push(Join {
1461            kind: JoinKind::Cross,
1462            this: Expression::Table(Box::new(builder_table_ref(table_name))),
1463            on: None,
1464            using: Vec::new(),
1465            use_inner_keyword: false,
1466            use_outer_keyword: false,
1467            deferred_condition: false,
1468            join_hint: None,
1469            match_condition: None,
1470            pivots: Vec::new(),
1471            comments: Vec::new(),
1472            nesting_group: 0,
1473            directed: false,
1474        });
1475        self
1476    }
1477
1478    /// Add a `LATERAL VIEW` clause for Hive/Spark user-defined table function (UDTF)
1479    /// expansion.
1480    ///
1481    /// `table_function` is the UDTF expression (e.g. `func("EXPLODE", [col("arr")])`),
1482    /// `table_alias` names the virtual table, and `column_aliases` name the output
1483    /// columns produced by the function.
1484    pub fn lateral_view<S: AsRef<str>>(
1485        mut self,
1486        table_function: Expr,
1487        table_alias: &str,
1488        column_aliases: impl IntoIterator<Item = S>,
1489    ) -> Self {
1490        self.select.lateral_views.push(LateralView {
1491            this: table_function.0,
1492            table_alias: Some(builder_identifier(table_alias)),
1493            column_aliases: column_aliases
1494                .into_iter()
1495                .map(|c| builder_identifier(c.as_ref()))
1496                .collect(),
1497            outer: false,
1498        });
1499        self
1500    }
1501
1502    /// Add a named `WINDOW` clause definition.
1503    ///
1504    /// The window `name` can then be referenced in window function OVER clauses
1505    /// elsewhere in the query. The definition is constructed via [`WindowDefBuilder`].
1506    /// Multiple calls append additional named windows.
1507    pub fn window(mut self, name: &str, def: WindowDefBuilder) -> Self {
1508        let named_window = NamedWindow {
1509            name: builder_identifier(name),
1510            spec: Over {
1511                window_name: None,
1512                partition_by: def.partition_by,
1513                order_by: def.order_by,
1514                frame: None,
1515                alias: None,
1516            },
1517        };
1518        match self.select.windows {
1519            Some(ref mut windows) => windows.push(named_window),
1520            None => self.select.windows = Some(vec![named_window]),
1521        }
1522        self
1523    }
1524
1525    /// Add a `FOR UPDATE` locking clause.
1526    ///
1527    /// Appends a `FOR UPDATE` lock to the SELECT statement. This is used by
1528    /// databases (PostgreSQL, MySQL, Oracle) to lock selected rows for update.
1529    pub fn for_update(mut self) -> Self {
1530        self.select.locks.push(Lock {
1531            update: Some(Box::new(Expression::Boolean(BooleanLiteral {
1532                value: true,
1533            }))),
1534            expressions: vec![],
1535            wait: None,
1536            key: None,
1537        });
1538        self
1539    }
1540
1541    /// Add a `FOR SHARE` locking clause.
1542    ///
1543    /// Appends a `FOR SHARE` lock to the SELECT statement. This allows other
1544    /// transactions to read the locked rows but prevents updates.
1545    pub fn for_share(mut self) -> Self {
1546        self.select.locks.push(Lock {
1547            update: None,
1548            expressions: vec![],
1549            wait: None,
1550            key: None,
1551        });
1552        self
1553    }
1554
1555    /// Add a query hint (e.g., Oracle `/*+ FULL(t) */`).
1556    ///
1557    /// Hints are rendered for Oracle, MySQL, Spark, Hive, Databricks, and PostgreSQL
1558    /// dialects. Multiple calls append additional hints.
1559    pub fn hint(mut self, hint_text: &str) -> Self {
1560        let hint_expr = HintExpression::Raw(hint_text.to_string());
1561        match &mut self.select.hint {
1562            Some(h) => h.expressions.push(hint_expr),
1563            None => {
1564                self.select.hint = Some(Hint {
1565                    expressions: vec![hint_expr],
1566                })
1567            }
1568        }
1569        self
1570    }
1571
1572    /// Convert this SELECT into a `CREATE TABLE AS SELECT` statement.
1573    ///
1574    /// Consumes the builder and returns an [`Expression::CreateTable`] with this
1575    /// query as the `as_select` source.
1576    ///
1577    /// # Examples
1578    ///
1579    /// ```
1580    /// use polyglot_sql::builder::*;
1581    ///
1582    /// let sql = polyglot_sql::generator::Generator::sql(
1583    ///     &select(["*"]).from("t").ctas("new_table")
1584    /// ).unwrap();
1585    /// assert_eq!(sql, "CREATE TABLE new_table AS SELECT * FROM t");
1586    /// ```
1587    pub fn ctas(self, table_name: &str) -> Expression {
1588        Expression::CreateTable(Box::new(CreateTable {
1589            name: builder_table_ref(table_name),
1590            on_cluster: None,
1591            columns: vec![],
1592            constraints: vec![],
1593            if_not_exists: false,
1594            temporary: false,
1595            or_replace: false,
1596            table_modifier: None,
1597            as_select: Some(self.build()),
1598            as_select_parenthesized: false,
1599            on_commit: None,
1600            clone_source: None,
1601            clone_at_clause: None,
1602            is_copy: false,
1603            shallow_clone: false,
1604            leading_comments: vec![],
1605            with_properties: vec![],
1606            teradata_post_name_options: vec![],
1607            with_data: None,
1608            with_statistics: None,
1609            teradata_indexes: vec![],
1610            with_cte: None,
1611            properties: vec![],
1612            partition_of: None,
1613            post_table_properties: vec![],
1614            mysql_table_options: vec![],
1615            inherits: vec![],
1616            on_property: None,
1617            copy_grants: false,
1618            using_template: None,
1619            rollup: None,
1620            uuid: None,
1621            with_partition_columns: vec![],
1622            with_connection: None,
1623        }))
1624    }
1625
1626    /// Combine this SELECT with another via `UNION` (duplicate elimination).
1627    ///
1628    /// Returns a [`SetOpBuilder`] for further chaining (e.g. `.order_by()`, `.limit()`).
1629    pub fn union(self, other: SelectBuilder) -> SetOpBuilder {
1630        SetOpBuilder::new(SetOpKind::Union, self, other, false)
1631    }
1632
1633    /// Combine this SELECT with another via `UNION ALL` (keep duplicates).
1634    ///
1635    /// Returns a [`SetOpBuilder`] for further chaining.
1636    pub fn union_all(self, other: SelectBuilder) -> SetOpBuilder {
1637        SetOpBuilder::new(SetOpKind::Union, self, other, true)
1638    }
1639
1640    /// Combine this SELECT with another via `INTERSECT` (rows common to both).
1641    ///
1642    /// Returns a [`SetOpBuilder`] for further chaining.
1643    pub fn intersect(self, other: SelectBuilder) -> SetOpBuilder {
1644        SetOpBuilder::new(SetOpKind::Intersect, self, other, false)
1645    }
1646
1647    /// Combine this SELECT with another via `EXCEPT` (rows in left but not right).
1648    ///
1649    /// Returns a [`SetOpBuilder`] for further chaining.
1650    pub fn except_(self, other: SelectBuilder) -> SetOpBuilder {
1651        SetOpBuilder::new(SetOpKind::Except, self, other, false)
1652    }
1653
1654    /// Consume this builder and produce the final [`Expression::Select`] AST node.
1655    pub fn build(self) -> Expression {
1656        Expression::Select(Box::new(self.select))
1657    }
1658
1659    /// Consume this builder, generate, and return the SQL string.
1660    ///
1661    /// Equivalent to calling `.build()` followed by [`Generator::sql()`]. Returns an
1662    /// empty string if generation fails.
1663    pub fn to_sql(self) -> String {
1664        Generator::sql(&self.build()).unwrap_or_default()
1665    }
1666}
1667
1668// ---------------------------------------------------------------------------
1669// DeleteBuilder
1670// ---------------------------------------------------------------------------
1671
1672/// Fluent builder for constructing `DELETE FROM` statements.
1673///
1674/// Created by the [`delete()`] entry-point function. Supports an optional `.where_()`
1675/// predicate.
1676pub struct DeleteBuilder {
1677    delete: Delete,
1678}
1679
1680impl DeleteBuilder {
1681    /// Set the WHERE clause to restrict which rows are deleted.
1682    pub fn where_(mut self, condition: Expr) -> Self {
1683        self.delete.where_clause = Some(Where { this: condition.0 });
1684        self
1685    }
1686
1687    /// Consume this builder and produce the final [`Expression::Delete`] AST node.
1688    pub fn build(self) -> Expression {
1689        Expression::Delete(Box::new(self.delete))
1690    }
1691
1692    /// Consume this builder, generate, and return the SQL string.
1693    pub fn to_sql(self) -> String {
1694        Generator::sql(&self.build()).unwrap_or_default()
1695    }
1696}
1697
1698// ---------------------------------------------------------------------------
1699// InsertBuilder
1700// ---------------------------------------------------------------------------
1701
1702/// Fluent builder for constructing `INSERT INTO` statements.
1703///
1704/// Created by the [`insert_into()`] entry-point function. Supports specifying target
1705/// columns via [`.columns()`](InsertBuilder::columns), row values via
1706/// [`.values()`](InsertBuilder::values) (can be called multiple times for multiple rows),
1707/// and INSERT ... SELECT via [`.query()`](InsertBuilder::query).
1708pub struct InsertBuilder {
1709    insert: Insert,
1710}
1711
1712impl InsertBuilder {
1713    /// Set the target column names for the INSERT statement.
1714    pub fn columns<I, S>(mut self, columns: I) -> Self
1715    where
1716        I: IntoIterator<Item = S>,
1717        S: AsRef<str>,
1718    {
1719        self.insert.columns = columns
1720            .into_iter()
1721            .map(|c| builder_identifier(c.as_ref()))
1722            .collect();
1723        self
1724    }
1725
1726    /// Append a row of values to the VALUES clause.
1727    ///
1728    /// Call this method multiple times to insert multiple rows in a single statement.
1729    pub fn values<I>(mut self, values: I) -> Self
1730    where
1731        I: IntoIterator<Item = Expr>,
1732    {
1733        self.insert
1734            .values
1735            .push(values.into_iter().map(|v| v.0).collect());
1736        self
1737    }
1738
1739    /// Set the source query for an `INSERT INTO ... SELECT ...` statement.
1740    ///
1741    /// When a query is set, the VALUES clause is ignored during generation.
1742    pub fn query(mut self, query: SelectBuilder) -> Self {
1743        self.insert.query = Some(query.build());
1744        self
1745    }
1746
1747    /// Consume this builder and produce the final [`Expression::Insert`] AST node.
1748    pub fn build(self) -> Expression {
1749        Expression::Insert(Box::new(self.insert))
1750    }
1751
1752    /// Consume this builder, generate, and return the SQL string.
1753    pub fn to_sql(self) -> String {
1754        Generator::sql(&self.build()).unwrap_or_default()
1755    }
1756}
1757
1758// ---------------------------------------------------------------------------
1759// UpdateBuilder
1760// ---------------------------------------------------------------------------
1761
1762/// Fluent builder for constructing `UPDATE` statements.
1763///
1764/// Created by the [`update()`] entry-point function. Supports column assignments via
1765/// [`.set()`](UpdateBuilder::set), an optional WHERE predicate, and an optional
1766/// FROM clause for PostgreSQL/Snowflake-style multi-table updates.
1767pub struct UpdateBuilder {
1768    update: Update,
1769}
1770
1771impl UpdateBuilder {
1772    /// Add a `SET column = value` assignment.
1773    ///
1774    /// Call this method multiple times to set multiple columns.
1775    pub fn set(mut self, column: &str, value: Expr) -> Self {
1776        self.update.set.push((builder_identifier(column), value.0));
1777        self
1778    }
1779
1780    /// Set the WHERE clause to restrict which rows are updated.
1781    pub fn where_(mut self, condition: Expr) -> Self {
1782        self.update.where_clause = Some(Where { this: condition.0 });
1783        self
1784    }
1785
1786    /// Set the FROM clause for PostgreSQL/Snowflake-style `UPDATE ... FROM ...` syntax.
1787    ///
1788    /// This allows joining against other tables within the UPDATE statement.
1789    pub fn from(mut self, table_name: &str) -> Self {
1790        self.update.from_clause = Some(From {
1791            expressions: vec![Expression::Table(Box::new(builder_table_ref(table_name)))],
1792        });
1793        self
1794    }
1795
1796    /// Consume this builder and produce the final [`Expression::Update`] AST node.
1797    pub fn build(self) -> Expression {
1798        Expression::Update(Box::new(self.update))
1799    }
1800
1801    /// Consume this builder, generate, and return the SQL string.
1802    pub fn to_sql(self) -> String {
1803        Generator::sql(&self.build()).unwrap_or_default()
1804    }
1805}
1806
1807// ---------------------------------------------------------------------------
1808// CaseBuilder
1809// ---------------------------------------------------------------------------
1810
1811/// Start building a searched CASE expression (`CASE WHEN cond THEN result ... END`).
1812///
1813/// A searched CASE evaluates each WHEN condition independently. Use [`case_of()`] for
1814/// a simple CASE that compares an operand against values.
1815///
1816/// # Examples
1817///
1818/// ```
1819/// use polyglot_sql::builder::*;
1820///
1821/// let expr = case()
1822///     .when(col("x").gt(lit(0)), lit("positive"))
1823///     .when(col("x").eq(lit(0)), lit("zero"))
1824///     .else_(lit("negative"))
1825///     .build();
1826/// assert_eq!(
1827///     expr.to_sql(),
1828///     "CASE WHEN x > 0 THEN 'positive' WHEN x = 0 THEN 'zero' ELSE 'negative' END"
1829/// );
1830/// ```
1831pub fn case() -> CaseBuilder {
1832    CaseBuilder {
1833        operand: None,
1834        whens: Vec::new(),
1835        else_: None,
1836    }
1837}
1838
1839/// Start building a simple CASE expression (`CASE operand WHEN value THEN result ... END`).
1840///
1841/// A simple CASE compares the `operand` against each WHEN value for equality. Use
1842/// [`case()`] for a searched CASE with arbitrary boolean conditions.
1843///
1844/// # Examples
1845///
1846/// ```
1847/// use polyglot_sql::builder::*;
1848///
1849/// let expr = case_of(col("status"))
1850///     .when(lit(1), lit("active"))
1851///     .when(lit(0), lit("inactive"))
1852///     .else_(lit("unknown"))
1853///     .build();
1854/// assert_eq!(
1855///     expr.to_sql(),
1856///     "CASE status WHEN 1 THEN 'active' WHEN 0 THEN 'inactive' ELSE 'unknown' END"
1857/// );
1858/// ```
1859pub fn case_of(operand: Expr) -> CaseBuilder {
1860    CaseBuilder {
1861        operand: Some(operand.0),
1862        whens: Vec::new(),
1863        else_: None,
1864    }
1865}
1866
1867/// Fluent builder for SQL `CASE` expressions (both searched and simple forms).
1868///
1869/// Created by [`case()`] (searched form) or [`case_of()`] (simple form). Add branches
1870/// with [`.when()`](CaseBuilder::when) and an optional default with
1871/// [`.else_()`](CaseBuilder::else_). Finalize with [`.build()`](CaseBuilder::build) to
1872/// get an [`Expr`], or [`.build_expr()`](CaseBuilder::build_expr) for a raw
1873/// [`Expression`].
1874pub struct CaseBuilder {
1875    operand: Option<Expression>,
1876    whens: Vec<(Expression, Expression)>,
1877    else_: Option<Expression>,
1878}
1879
1880impl CaseBuilder {
1881    /// Add a `WHEN condition THEN result` branch to the CASE expression.
1882    ///
1883    /// For a searched CASE ([`case()`]), `condition` is a boolean predicate. For a simple
1884    /// CASE ([`case_of()`]), `condition` is the value to compare against the operand.
1885    pub fn when(mut self, condition: Expr, result: Expr) -> Self {
1886        self.whens.push((condition.0, result.0));
1887        self
1888    }
1889
1890    /// Set the `ELSE result` default branch of the CASE expression.
1891    ///
1892    /// If not called, the CASE expression has no ELSE clause (implicitly NULL when
1893    /// no WHEN matches).
1894    pub fn else_(mut self, result: Expr) -> Self {
1895        self.else_ = Some(result.0);
1896        self
1897    }
1898
1899    /// Consume this builder and produce an [`Expr`] wrapping the CASE expression.
1900    pub fn build(self) -> Expr {
1901        Expr(self.build_expr())
1902    }
1903
1904    /// Consume this builder and produce the raw [`Expression::Case`] AST node.
1905    ///
1906    /// Use this instead of [`.build()`](CaseBuilder::build) when you need the
1907    /// [`Expression`] directly rather than an [`Expr`] wrapper.
1908    pub fn build_expr(self) -> Expression {
1909        Expression::Case(Box::new(Case {
1910            operand: self.operand,
1911            whens: self.whens,
1912            else_: self.else_,
1913            comments: Vec::new(),
1914            inferred_type: None,
1915        }))
1916    }
1917}
1918
1919// ---------------------------------------------------------------------------
1920// Subquery builders
1921// ---------------------------------------------------------------------------
1922
1923/// Wrap a [`SelectBuilder`] as a named subquery for use in FROM or JOIN clauses.
1924///
1925/// The resulting [`Expr`] can be passed to [`SelectBuilder::from_expr()`] or used
1926/// in a join condition.
1927///
1928/// # Examples
1929///
1930/// ```
1931/// use polyglot_sql::builder::*;
1932///
1933/// let inner = select(["id", "name"]).from("users").where_(col("active").eq(boolean(true)));
1934/// let sql = select(["sub.id"])
1935///     .from_expr(subquery(inner, "sub"))
1936///     .to_sql();
1937/// assert_eq!(
1938///     sql,
1939///     "SELECT sub.id FROM (SELECT id, name FROM users WHERE active = TRUE) AS sub"
1940/// );
1941/// ```
1942pub fn subquery(query: SelectBuilder, alias_name: &str) -> Expr {
1943    subquery_expr(query.build(), alias_name)
1944}
1945
1946/// Wrap an existing [`Expression`] as a named subquery.
1947///
1948/// This is the lower-level version of [`subquery()`] that accepts a pre-built
1949/// [`Expression`] instead of a [`SelectBuilder`].
1950pub fn subquery_expr(expr: Expression, alias_name: &str) -> Expr {
1951    Expr(Expression::Subquery(Box::new(Subquery {
1952        this: expr,
1953        alias: Some(builder_identifier(alias_name)),
1954        column_aliases: Vec::new(),
1955        order_by: None,
1956        limit: None,
1957        offset: None,
1958        distribute_by: None,
1959        sort_by: None,
1960        cluster_by: None,
1961        lateral: false,
1962        modifiers_inside: true,
1963        trailing_comments: Vec::new(),
1964        inferred_type: None,
1965    })))
1966}
1967
1968// ---------------------------------------------------------------------------
1969// SetOpBuilder
1970// ---------------------------------------------------------------------------
1971
1972/// Internal enum distinguishing the three kinds of set operations.
1973#[derive(Debug, Clone, Copy)]
1974enum SetOpKind {
1975    Union,
1976    Intersect,
1977    Except,
1978}
1979
1980/// Fluent builder for `UNION`, `INTERSECT`, and `EXCEPT` set operations.
1981///
1982/// Created by the free functions [`union()`], [`union_all()`], [`intersect()`],
1983/// [`intersect_all()`], [`except_()`], [`except_all()`], or the corresponding methods
1984/// on [`SelectBuilder`]. Supports optional `.order_by()`, `.limit()`, and `.offset()`
1985/// clauses applied to the combined result.
1986///
1987/// # Examples
1988///
1989/// ```
1990/// use polyglot_sql::builder::*;
1991///
1992/// let sql = union_all(
1993///     select(["id"]).from("a"),
1994///     select(["id"]).from("b"),
1995/// )
1996/// .order_by(["id"])
1997/// .limit(10)
1998/// .to_sql();
1999/// ```
2000pub struct SetOpBuilder {
2001    kind: SetOpKind,
2002    left: Expression,
2003    right: Expression,
2004    all: bool,
2005    order_by: Option<OrderBy>,
2006    limit: Option<Box<Expression>>,
2007    offset: Option<Box<Expression>>,
2008}
2009
2010impl SetOpBuilder {
2011    fn new(kind: SetOpKind, left: SelectBuilder, right: SelectBuilder, all: bool) -> Self {
2012        SetOpBuilder {
2013            kind,
2014            left: left.build(),
2015            right: right.build(),
2016            all,
2017            order_by: None,
2018            limit: None,
2019            offset: None,
2020        }
2021    }
2022
2023    /// Add an ORDER BY clause applied to the combined set operation result.
2024    ///
2025    /// Expressions not already wrapped with [`.asc()`](Expr::asc) or
2026    /// [`.desc()`](Expr::desc) default to ascending order.
2027    pub fn order_by<I, E>(mut self, expressions: I) -> Self
2028    where
2029        I: IntoIterator<Item = E>,
2030        E: IntoExpr,
2031    {
2032        self.order_by = Some(OrderBy {
2033            siblings: false,
2034            comments: Vec::new(),
2035            expressions: expressions
2036                .into_iter()
2037                .map(|e| {
2038                    let expr = e.into_expr().0;
2039                    match expr {
2040                        Expression::Ordered(o) => *o,
2041                        other => Ordered {
2042                            this: other,
2043                            desc: false,
2044                            nulls_first: None,
2045                            explicit_asc: false,
2046                            with_fill: None,
2047                        },
2048                    }
2049                })
2050                .collect(),
2051        });
2052        self
2053    }
2054
2055    /// Restrict the combined set operation result to `count` rows.
2056    pub fn limit(mut self, count: usize) -> Self {
2057        self.limit = Some(Box::new(Expression::Literal(Box::new(Literal::Number(
2058            count.to_string(),
2059        )))));
2060        self
2061    }
2062
2063    /// Skip the first `count` rows from the combined set operation result.
2064    pub fn offset(mut self, count: usize) -> Self {
2065        self.offset = Some(Box::new(Expression::Literal(Box::new(Literal::Number(
2066            count.to_string(),
2067        )))));
2068        self
2069    }
2070
2071    /// Consume this builder and produce the final set operation [`Expression`] AST node.
2072    ///
2073    /// The returned expression is one of [`Expression::Union`], [`Expression::Intersect`],
2074    /// or [`Expression::Except`] depending on how the builder was created.
2075    pub fn build(self) -> Expression {
2076        match self.kind {
2077            SetOpKind::Union => Expression::Union(Box::new(Union {
2078                left: self.left,
2079                right: self.right,
2080                all: self.all,
2081                distinct: false,
2082                with: None,
2083                order_by: self.order_by,
2084                limit: self.limit,
2085                offset: self.offset,
2086                distribute_by: None,
2087                sort_by: None,
2088                cluster_by: None,
2089                by_name: false,
2090                side: None,
2091                kind: None,
2092                corresponding: false,
2093                strict: false,
2094                on_columns: Vec::new(),
2095            })),
2096            SetOpKind::Intersect => Expression::Intersect(Box::new(Intersect {
2097                left: self.left,
2098                right: self.right,
2099                all: self.all,
2100                distinct: false,
2101                with: None,
2102                order_by: self.order_by,
2103                limit: self.limit,
2104                offset: self.offset,
2105                distribute_by: None,
2106                sort_by: None,
2107                cluster_by: None,
2108                by_name: false,
2109                side: None,
2110                kind: None,
2111                corresponding: false,
2112                strict: false,
2113                on_columns: Vec::new(),
2114            })),
2115            SetOpKind::Except => Expression::Except(Box::new(Except {
2116                left: self.left,
2117                right: self.right,
2118                all: self.all,
2119                distinct: false,
2120                with: None,
2121                order_by: self.order_by,
2122                limit: self.limit,
2123                offset: self.offset,
2124                distribute_by: None,
2125                sort_by: None,
2126                cluster_by: None,
2127                by_name: false,
2128                side: None,
2129                kind: None,
2130                corresponding: false,
2131                strict: false,
2132                on_columns: Vec::new(),
2133            })),
2134        }
2135    }
2136
2137    /// Consume this builder, generate, and return the SQL string.
2138    pub fn to_sql(self) -> String {
2139        Generator::sql(&self.build()).unwrap_or_default()
2140    }
2141}
2142
2143/// Create a `UNION` (duplicate elimination) of two SELECT queries.
2144///
2145/// Returns a [`SetOpBuilder`] for optional ORDER BY / LIMIT / OFFSET chaining.
2146pub fn union(left: SelectBuilder, right: SelectBuilder) -> SetOpBuilder {
2147    SetOpBuilder::new(SetOpKind::Union, left, right, false)
2148}
2149
2150/// Create a `UNION ALL` (keep duplicates) of two SELECT queries.
2151///
2152/// Returns a [`SetOpBuilder`] for optional ORDER BY / LIMIT / OFFSET chaining.
2153pub fn union_all(left: SelectBuilder, right: SelectBuilder) -> SetOpBuilder {
2154    SetOpBuilder::new(SetOpKind::Union, left, right, true)
2155}
2156
2157/// Create an `INTERSECT` (rows common to both) of two SELECT queries.
2158///
2159/// Returns a [`SetOpBuilder`] for optional ORDER BY / LIMIT / OFFSET chaining.
2160pub fn intersect(left: SelectBuilder, right: SelectBuilder) -> SetOpBuilder {
2161    SetOpBuilder::new(SetOpKind::Intersect, left, right, false)
2162}
2163
2164/// Create an `INTERSECT ALL` (keep duplicate common rows) of two SELECT queries.
2165///
2166/// Returns a [`SetOpBuilder`] for optional ORDER BY / LIMIT / OFFSET chaining.
2167pub fn intersect_all(left: SelectBuilder, right: SelectBuilder) -> SetOpBuilder {
2168    SetOpBuilder::new(SetOpKind::Intersect, left, right, true)
2169}
2170
2171/// Create an `EXCEPT` (rows in left but not right) of two SELECT queries.
2172///
2173/// Returns a [`SetOpBuilder`] for optional ORDER BY / LIMIT / OFFSET chaining.
2174pub fn except_(left: SelectBuilder, right: SelectBuilder) -> SetOpBuilder {
2175    SetOpBuilder::new(SetOpKind::Except, left, right, false)
2176}
2177
2178/// Create an `EXCEPT ALL` (keep duplicate difference rows) of two SELECT queries.
2179///
2180/// Returns a [`SetOpBuilder`] for optional ORDER BY / LIMIT / OFFSET chaining.
2181pub fn except_all(left: SelectBuilder, right: SelectBuilder) -> SetOpBuilder {
2182    SetOpBuilder::new(SetOpKind::Except, left, right, true)
2183}
2184
2185// ---------------------------------------------------------------------------
2186// WindowDefBuilder
2187// ---------------------------------------------------------------------------
2188
2189/// Builder for constructing named `WINDOW` clause definitions.
2190///
2191/// Used with [`SelectBuilder::window()`] to define reusable window specifications.
2192/// Supports PARTITION BY and ORDER BY clauses.
2193///
2194/// # Examples
2195///
2196/// ```
2197/// use polyglot_sql::builder::*;
2198///
2199/// let sql = select(["id"])
2200///     .from("t")
2201///     .window(
2202///         "w",
2203///         WindowDefBuilder::new()
2204///             .partition_by(["dept"])
2205///             .order_by([col("salary").desc()]),
2206///     )
2207///     .to_sql();
2208/// ```
2209pub struct WindowDefBuilder {
2210    partition_by: Vec<Expression>,
2211    order_by: Vec<Ordered>,
2212}
2213
2214impl WindowDefBuilder {
2215    /// Create a new, empty window definition builder with no partitioning or ordering.
2216    pub fn new() -> Self {
2217        WindowDefBuilder {
2218            partition_by: Vec::new(),
2219            order_by: Vec::new(),
2220        }
2221    }
2222
2223    /// Set the PARTITION BY expressions for the window definition.
2224    pub fn partition_by<I, E>(mut self, expressions: I) -> Self
2225    where
2226        I: IntoIterator<Item = E>,
2227        E: IntoExpr,
2228    {
2229        self.partition_by = expressions.into_iter().map(|e| e.into_expr().0).collect();
2230        self
2231    }
2232
2233    /// Set the ORDER BY expressions for the window definition.
2234    ///
2235    /// Expressions not already wrapped with [`.asc()`](Expr::asc) or
2236    /// [`.desc()`](Expr::desc) default to ascending order.
2237    pub fn order_by<I, E>(mut self, expressions: I) -> Self
2238    where
2239        I: IntoIterator<Item = E>,
2240        E: IntoExpr,
2241    {
2242        self.order_by = expressions
2243            .into_iter()
2244            .map(|e| {
2245                let expr = e.into_expr().0;
2246                match expr {
2247                    Expression::Ordered(o) => *o,
2248                    other => Ordered {
2249                        this: other,
2250                        desc: false,
2251                        nulls_first: None,
2252                        explicit_asc: false,
2253                        with_fill: None,
2254                    },
2255                }
2256            })
2257            .collect();
2258        self
2259    }
2260}
2261
2262// ---------------------------------------------------------------------------
2263// Trait: IntoExpr
2264// ---------------------------------------------------------------------------
2265
2266/// Conversion trait for types that can be turned into an [`Expr`].
2267///
2268/// This trait is implemented for:
2269///
2270/// - [`Expr`] -- returned as-is.
2271/// - `&str` and `String` -- converted to a column reference via [`col()`].
2272/// - [`Expression`] -- wrapped directly in an [`Expr`].
2273///
2274/// Note: `&str`/`String` inputs are treated as identifiers, not SQL string
2275/// literals. Use [`lit()`] for literal values.
2276///
2277/// It is used as a generic bound throughout the builder API so that functions like
2278/// [`select()`], [`SelectBuilder::order_by()`], and [`SelectBuilder::group_by()`] can
2279/// accept plain strings, [`Expr`] values, or raw [`Expression`] nodes interchangeably.
2280pub trait IntoExpr {
2281    /// Convert this value into an [`Expr`].
2282    fn into_expr(self) -> Expr;
2283}
2284
2285impl IntoExpr for Expr {
2286    fn into_expr(self) -> Expr {
2287        self
2288    }
2289}
2290
2291impl IntoExpr for &str {
2292    /// Convert a string slice to a column reference via [`col()`].
2293    fn into_expr(self) -> Expr {
2294        col(self)
2295    }
2296}
2297
2298impl IntoExpr for String {
2299    /// Convert an owned string to a column reference via [`col()`].
2300    fn into_expr(self) -> Expr {
2301        col(&self)
2302    }
2303}
2304
2305impl IntoExpr for Expression {
2306    /// Wrap a raw [`Expression`] in an [`Expr`].
2307    fn into_expr(self) -> Expr {
2308        Expr(self)
2309    }
2310}
2311
2312// ---------------------------------------------------------------------------
2313// Trait: IntoLiteral
2314// ---------------------------------------------------------------------------
2315
2316/// Conversion trait for types that can be turned into a SQL literal [`Expr`].
2317///
2318/// This trait is used by [`lit()`] to accept various Rust primitive types and convert
2319/// them into the appropriate SQL literal representation.
2320///
2321/// Implemented for:
2322///
2323/// - `&str`, `String` -- produce a SQL string literal (e.g. `'hello'`).
2324/// - `i32`, `i64`, `usize`, `f64` -- produce a SQL numeric literal (e.g. `42`, `3.14`).
2325/// - `bool` -- produce a SQL boolean literal (`TRUE` or `FALSE`).
2326pub trait IntoLiteral {
2327    /// Convert this value into a literal [`Expr`].
2328    fn into_literal(self) -> Expr;
2329}
2330
2331impl IntoLiteral for &str {
2332    /// Produce a SQL string literal (e.g. `'hello'`).
2333    fn into_literal(self) -> Expr {
2334        Expr(Expression::Literal(Box::new(Literal::String(
2335            self.to_string(),
2336        ))))
2337    }
2338}
2339
2340impl IntoLiteral for String {
2341    /// Produce a SQL string literal from an owned string.
2342    fn into_literal(self) -> Expr {
2343        Expr(Expression::Literal(Box::new(Literal::String(self))))
2344    }
2345}
2346
2347impl IntoLiteral for i64 {
2348    /// Produce a SQL numeric literal from a 64-bit integer.
2349    fn into_literal(self) -> Expr {
2350        Expr(Expression::Literal(Box::new(Literal::Number(
2351            self.to_string(),
2352        ))))
2353    }
2354}
2355
2356impl IntoLiteral for i32 {
2357    /// Produce a SQL numeric literal from a 32-bit integer.
2358    fn into_literal(self) -> Expr {
2359        Expr(Expression::Literal(Box::new(Literal::Number(
2360            self.to_string(),
2361        ))))
2362    }
2363}
2364
2365impl IntoLiteral for usize {
2366    /// Produce a SQL numeric literal from a `usize`.
2367    fn into_literal(self) -> Expr {
2368        Expr(Expression::Literal(Box::new(Literal::Number(
2369            self.to_string(),
2370        ))))
2371    }
2372}
2373
2374impl IntoLiteral for f64 {
2375    /// Produce a SQL numeric literal from a 64-bit float.
2376    fn into_literal(self) -> Expr {
2377        Expr(Expression::Literal(Box::new(Literal::Number(
2378            self.to_string(),
2379        ))))
2380    }
2381}
2382
2383impl IntoLiteral for bool {
2384    /// Produce a SQL boolean literal (`TRUE` or `FALSE`).
2385    fn into_literal(self) -> Expr {
2386        Expr(Expression::Boolean(BooleanLiteral { value: self }))
2387    }
2388}
2389
2390// ---------------------------------------------------------------------------
2391// Helpers
2392// ---------------------------------------------------------------------------
2393
2394fn binary_op(left: Expression, right: Expression) -> BinaryOp {
2395    BinaryOp {
2396        left,
2397        right,
2398        left_comments: Vec::new(),
2399        operator_comments: Vec::new(),
2400        trailing_comments: Vec::new(),
2401        inferred_type: None,
2402    }
2403}
2404
2405// ---------------------------------------------------------------------------
2406// MergeBuilder
2407// ---------------------------------------------------------------------------
2408
2409/// Start building a `MERGE INTO` statement targeting the given table.
2410///
2411/// Returns a [`MergeBuilder`] which supports `.using()`, `.when_matched_update()`,
2412/// `.when_matched_delete()`, and `.when_not_matched_insert()`.
2413///
2414/// # Examples
2415///
2416/// ```
2417/// use polyglot_sql::builder::*;
2418///
2419/// let sql = merge_into("target")
2420///     .using("source", col("target.id").eq(col("source.id")))
2421///     .when_matched_update(vec![("name", col("source.name"))])
2422///     .when_not_matched_insert(&["id", "name"], vec![col("source.id"), col("source.name")])
2423///     .to_sql();
2424/// assert!(sql.contains("MERGE INTO"));
2425/// ```
2426pub fn merge_into(target: &str) -> MergeBuilder {
2427    MergeBuilder {
2428        target: Expression::Table(Box::new(builder_table_ref(target))),
2429        using: None,
2430        on: None,
2431        whens: Vec::new(),
2432    }
2433}
2434
2435/// Fluent builder for constructing `MERGE INTO` statements.
2436///
2437/// Created by the [`merge_into()`] entry-point function.
2438pub struct MergeBuilder {
2439    target: Expression,
2440    using: Option<Expression>,
2441    on: Option<Expression>,
2442    whens: Vec<Expression>,
2443}
2444
2445impl MergeBuilder {
2446    /// Set the source table and ON join condition.
2447    pub fn using(mut self, source: &str, on: Expr) -> Self {
2448        self.using = Some(Expression::Table(Box::new(builder_table_ref(source))));
2449        self.on = Some(on.0);
2450        self
2451    }
2452
2453    /// Add a `WHEN MATCHED THEN UPDATE SET` clause.
2454    pub fn when_matched_update(mut self, assignments: Vec<(&str, Expr)>) -> Self {
2455        let eqs: Vec<Expression> = assignments
2456            .into_iter()
2457            .map(|(col_name, val)| {
2458                Expression::Eq(Box::new(BinaryOp {
2459                    left: Expression::boxed_column(Column {
2460                        name: builder_identifier(col_name),
2461                        table: None,
2462                        join_mark: false,
2463                        trailing_comments: Vec::new(),
2464                        span: None,
2465                        inferred_type: None,
2466                    }),
2467                    right: val.0,
2468                    left_comments: Vec::new(),
2469                    operator_comments: Vec::new(),
2470                    trailing_comments: Vec::new(),
2471                    inferred_type: None,
2472                }))
2473            })
2474            .collect();
2475
2476        let action = Expression::Tuple(Box::new(Tuple {
2477            expressions: vec![
2478                Expression::Var(Box::new(Var {
2479                    this: "UPDATE".to_string(),
2480                })),
2481                Expression::Tuple(Box::new(Tuple { expressions: eqs })),
2482            ],
2483        }));
2484
2485        let when = Expression::When(Box::new(When {
2486            matched: Some(Box::new(Expression::Boolean(BooleanLiteral {
2487                value: true,
2488            }))),
2489            source: None,
2490            condition: None,
2491            then: Box::new(action),
2492        }));
2493        self.whens.push(when);
2494        self
2495    }
2496
2497    /// Add a `WHEN MATCHED THEN UPDATE SET` clause with an additional condition.
2498    pub fn when_matched_update_where(
2499        mut self,
2500        condition: Expr,
2501        assignments: Vec<(&str, Expr)>,
2502    ) -> Self {
2503        let eqs: Vec<Expression> = assignments
2504            .into_iter()
2505            .map(|(col_name, val)| {
2506                Expression::Eq(Box::new(BinaryOp {
2507                    left: Expression::boxed_column(Column {
2508                        name: builder_identifier(col_name),
2509                        table: None,
2510                        join_mark: false,
2511                        trailing_comments: Vec::new(),
2512                        span: None,
2513                        inferred_type: None,
2514                    }),
2515                    right: val.0,
2516                    left_comments: Vec::new(),
2517                    operator_comments: Vec::new(),
2518                    trailing_comments: Vec::new(),
2519                    inferred_type: None,
2520                }))
2521            })
2522            .collect();
2523
2524        let action = Expression::Tuple(Box::new(Tuple {
2525            expressions: vec![
2526                Expression::Var(Box::new(Var {
2527                    this: "UPDATE".to_string(),
2528                })),
2529                Expression::Tuple(Box::new(Tuple { expressions: eqs })),
2530            ],
2531        }));
2532
2533        let when = Expression::When(Box::new(When {
2534            matched: Some(Box::new(Expression::Boolean(BooleanLiteral {
2535                value: true,
2536            }))),
2537            source: None,
2538            condition: Some(Box::new(condition.0)),
2539            then: Box::new(action),
2540        }));
2541        self.whens.push(when);
2542        self
2543    }
2544
2545    /// Add a `WHEN MATCHED THEN DELETE` clause.
2546    pub fn when_matched_delete(mut self) -> Self {
2547        let action = Expression::Var(Box::new(Var {
2548            this: "DELETE".to_string(),
2549        }));
2550
2551        let when = Expression::When(Box::new(When {
2552            matched: Some(Box::new(Expression::Boolean(BooleanLiteral {
2553                value: true,
2554            }))),
2555            source: None,
2556            condition: None,
2557            then: Box::new(action),
2558        }));
2559        self.whens.push(when);
2560        self
2561    }
2562
2563    /// Add a `WHEN NOT MATCHED THEN INSERT (cols) VALUES (vals)` clause.
2564    pub fn when_not_matched_insert(mut self, columns: &[&str], values: Vec<Expr>) -> Self {
2565        let col_exprs: Vec<Expression> = columns
2566            .iter()
2567            .map(|c| {
2568                Expression::boxed_column(Column {
2569                    name: builder_identifier(c),
2570                    table: None,
2571                    join_mark: false,
2572                    trailing_comments: Vec::new(),
2573                    span: None,
2574                    inferred_type: None,
2575                })
2576            })
2577            .collect();
2578        let val_exprs: Vec<Expression> = values.into_iter().map(|v| v.0).collect();
2579
2580        let action = Expression::Tuple(Box::new(Tuple {
2581            expressions: vec![
2582                Expression::Var(Box::new(Var {
2583                    this: "INSERT".to_string(),
2584                })),
2585                Expression::Tuple(Box::new(Tuple {
2586                    expressions: col_exprs,
2587                })),
2588                Expression::Tuple(Box::new(Tuple {
2589                    expressions: val_exprs,
2590                })),
2591            ],
2592        }));
2593
2594        let when = Expression::When(Box::new(When {
2595            matched: Some(Box::new(Expression::Boolean(BooleanLiteral {
2596                value: false,
2597            }))),
2598            source: None,
2599            condition: None,
2600            then: Box::new(action),
2601        }));
2602        self.whens.push(when);
2603        self
2604    }
2605
2606    /// Consume this builder and produce the final [`Expression::Merge`] AST node.
2607    pub fn build(self) -> Expression {
2608        let whens_expr = Expression::Whens(Box::new(Whens {
2609            expressions: self.whens,
2610        }));
2611
2612        Expression::Merge(Box::new(Merge {
2613            this: Box::new(self.target),
2614            using: Box::new(
2615                self.using
2616                    .unwrap_or(Expression::Null(crate::expressions::Null)),
2617            ),
2618            on: self.on.map(Box::new),
2619            using_cond: None,
2620            whens: Some(Box::new(whens_expr)),
2621            with_: None,
2622            returning: None,
2623        }))
2624    }
2625
2626    /// Consume this builder, generate, and return the SQL string.
2627    pub fn to_sql(self) -> String {
2628        Generator::sql(&self.build()).unwrap_or_default()
2629    }
2630}
2631
2632fn parse_simple_data_type(name: &str) -> DataType {
2633    let upper = name.trim().to_uppercase();
2634    match upper.as_str() {
2635        "INT" | "INTEGER" => DataType::Int {
2636            length: None,
2637            integer_spelling: upper == "INTEGER",
2638        },
2639        "BIGINT" => DataType::BigInt { length: None },
2640        "SMALLINT" => DataType::SmallInt { length: None },
2641        "TINYINT" => DataType::TinyInt { length: None },
2642        "FLOAT" => DataType::Float {
2643            precision: None,
2644            scale: None,
2645            real_spelling: false,
2646        },
2647        "DOUBLE" => DataType::Double {
2648            precision: None,
2649            scale: None,
2650        },
2651        "BOOLEAN" | "BOOL" => DataType::Boolean,
2652        "TEXT" => DataType::Text,
2653        "DATE" => DataType::Date,
2654        "TIMESTAMP" => DataType::Timestamp {
2655            precision: None,
2656            timezone: false,
2657        },
2658        "VARCHAR" => DataType::VarChar {
2659            length: None,
2660            parenthesized_length: false,
2661        },
2662        "CHAR" => DataType::Char { length: None },
2663        _ => {
2664            // Try to parse as a full type via the parser for complex types
2665            if let Ok(ast) =
2666                crate::parser::Parser::parse_sql(&format!("SELECT CAST(x AS {})", name))
2667            {
2668                if let Expression::Select(s) = &ast[0] {
2669                    if let Some(Expression::Cast(c)) = s.expressions.first() {
2670                        return c.to.clone();
2671                    }
2672                }
2673            }
2674            // Fallback: treat as a custom type
2675            DataType::Custom {
2676                name: name.to_string(),
2677            }
2678        }
2679    }
2680}
2681
2682#[cfg(test)]
2683mod tests {
2684    use super::*;
2685
2686    #[test]
2687    fn test_simple_select() {
2688        let sql = select(["id", "name"]).from("users").to_sql();
2689        assert_eq!(sql, "SELECT id, name FROM users");
2690    }
2691
2692    #[test]
2693    fn test_builder_quotes_unsafe_identifier_tokens() {
2694        let sql = select(["Name; DROP TABLE titanic"]).to_sql();
2695        assert_eq!(sql, r#"SELECT "Name; DROP TABLE titanic""#);
2696    }
2697
2698    #[test]
2699    fn test_builder_string_literal_requires_lit() {
2700        let sql = select([lit("Name; DROP TABLE titanic")]).to_sql();
2701        assert_eq!(sql, "SELECT 'Name; DROP TABLE titanic'");
2702    }
2703
2704    #[test]
2705    fn test_builder_quotes_unsafe_table_name_tokens() {
2706        let sql = select(["id"]).from("users; DROP TABLE x").to_sql();
2707        assert_eq!(sql, r#"SELECT id FROM "users; DROP TABLE x""#);
2708    }
2709
2710    #[test]
2711    fn test_select_star() {
2712        let sql = select([star()]).from("users").to_sql();
2713        assert_eq!(sql, "SELECT * FROM users");
2714    }
2715
2716    #[test]
2717    fn test_select_with_where() {
2718        let sql = select(["id", "name"])
2719            .from("users")
2720            .where_(col("age").gt(lit(18)))
2721            .to_sql();
2722        assert_eq!(sql, "SELECT id, name FROM users WHERE age > 18");
2723    }
2724
2725    #[test]
2726    fn test_select_with_join() {
2727        let sql = select(["u.id", "o.amount"])
2728            .from("users")
2729            .join("orders", col("u.id").eq(col("o.user_id")))
2730            .to_sql();
2731        assert_eq!(
2732            sql,
2733            "SELECT u.id, o.amount FROM users JOIN orders ON u.id = o.user_id"
2734        );
2735    }
2736
2737    #[test]
2738    fn test_select_with_group_by_having() {
2739        let sql = select([col("dept"), func("COUNT", [star()]).alias("cnt")])
2740            .from("employees")
2741            .group_by(["dept"])
2742            .having(func("COUNT", [star()]).gt(lit(5)))
2743            .to_sql();
2744        assert_eq!(
2745            sql,
2746            "SELECT dept, COUNT(*) AS cnt FROM employees GROUP BY dept HAVING COUNT(*) > 5"
2747        );
2748    }
2749
2750    #[test]
2751    fn test_select_with_order_limit_offset() {
2752        let sql = select(["id", "name"])
2753            .from("users")
2754            .order_by(["name"])
2755            .limit(10)
2756            .offset(20)
2757            .to_sql();
2758        assert_eq!(
2759            sql,
2760            "SELECT id, name FROM users ORDER BY name LIMIT 10 OFFSET 20"
2761        );
2762    }
2763
2764    #[test]
2765    fn test_select_distinct() {
2766        let sql = select(["name"]).from("users").distinct().to_sql();
2767        assert_eq!(sql, "SELECT DISTINCT name FROM users");
2768    }
2769
2770    #[test]
2771    fn test_insert_values() {
2772        let sql = insert_into("users")
2773            .columns(["id", "name"])
2774            .values([lit(1), lit("Alice")])
2775            .values([lit(2), lit("Bob")])
2776            .to_sql();
2777        assert_eq!(
2778            sql,
2779            "INSERT INTO users (id, name) VALUES (1, 'Alice'), (2, 'Bob')"
2780        );
2781    }
2782
2783    #[test]
2784    fn test_insert_select() {
2785        let sql = insert_into("archive")
2786            .columns(["id", "name"])
2787            .query(select(["id", "name"]).from("users"))
2788            .to_sql();
2789        assert_eq!(
2790            sql,
2791            "INSERT INTO archive (id, name) SELECT id, name FROM users"
2792        );
2793    }
2794
2795    #[test]
2796    fn test_update() {
2797        let sql = update("users")
2798            .set("name", lit("Bob"))
2799            .set("age", lit(30))
2800            .where_(col("id").eq(lit(1)))
2801            .to_sql();
2802        assert_eq!(sql, "UPDATE users SET name = 'Bob', age = 30 WHERE id = 1");
2803    }
2804
2805    #[test]
2806    fn test_delete() {
2807        let sql = delete("users").where_(col("id").eq(lit(1))).to_sql();
2808        assert_eq!(sql, "DELETE FROM users WHERE id = 1");
2809    }
2810
2811    #[test]
2812    fn test_complex_where() {
2813        let sql = select(["id"])
2814            .from("users")
2815            .where_(
2816                col("age")
2817                    .gte(lit(18))
2818                    .and(col("active").eq(boolean(true)))
2819                    .and(col("name").like(lit("%test%"))),
2820            )
2821            .to_sql();
2822        assert_eq!(
2823            sql,
2824            "SELECT id FROM users WHERE age >= 18 AND active = TRUE AND name LIKE '%test%'"
2825        );
2826    }
2827
2828    #[test]
2829    fn test_in_list() {
2830        let sql = select(["id"])
2831            .from("users")
2832            .where_(col("status").in_list([lit("active"), lit("pending")]))
2833            .to_sql();
2834        assert_eq!(
2835            sql,
2836            "SELECT id FROM users WHERE status IN ('active', 'pending')"
2837        );
2838    }
2839
2840    #[test]
2841    fn test_between() {
2842        let sql = select(["id"])
2843            .from("orders")
2844            .where_(col("amount").between(lit(100), lit(500)))
2845            .to_sql();
2846        assert_eq!(
2847            sql,
2848            "SELECT id FROM orders WHERE amount BETWEEN 100 AND 500"
2849        );
2850    }
2851
2852    #[test]
2853    fn test_is_null() {
2854        let sql = select(["id"])
2855            .from("users")
2856            .where_(col("email").is_null())
2857            .to_sql();
2858        assert_eq!(sql, "SELECT id FROM users WHERE email IS NULL");
2859    }
2860
2861    #[test]
2862    fn test_arithmetic() {
2863        let sql = select([col("price").mul(col("quantity")).alias("total")])
2864            .from("items")
2865            .to_sql();
2866        assert_eq!(sql, "SELECT price * quantity AS total FROM items");
2867    }
2868
2869    #[test]
2870    fn test_cast() {
2871        let sql = select([col("id").cast("VARCHAR")]).from("users").to_sql();
2872        assert_eq!(sql, "SELECT CAST(id AS VARCHAR) FROM users");
2873    }
2874
2875    #[test]
2876    fn test_from_starter() {
2877        let sql = from("users").select_cols(["id", "name"]).to_sql();
2878        assert_eq!(sql, "SELECT id, name FROM users");
2879    }
2880
2881    #[test]
2882    fn test_qualified_column() {
2883        let sql = select([col("u.id"), col("u.name")]).from("users").to_sql();
2884        assert_eq!(sql, "SELECT u.id, u.name FROM users");
2885    }
2886
2887    #[test]
2888    fn test_not_condition() {
2889        let sql = select(["id"])
2890            .from("users")
2891            .where_(not(col("active").eq(boolean(true))))
2892            .to_sql();
2893        assert_eq!(sql, "SELECT id FROM users WHERE NOT active = TRUE");
2894    }
2895
2896    #[test]
2897    fn test_order_by_desc() {
2898        let sql = select(["id", "name"])
2899            .from("users")
2900            .order_by([col("name").desc()])
2901            .to_sql();
2902        assert_eq!(sql, "SELECT id, name FROM users ORDER BY name DESC");
2903    }
2904
2905    #[test]
2906    fn test_left_join() {
2907        let sql = select(["u.id", "o.amount"])
2908            .from("users")
2909            .left_join("orders", col("u.id").eq(col("o.user_id")))
2910            .to_sql();
2911        assert_eq!(
2912            sql,
2913            "SELECT u.id, o.amount FROM users LEFT JOIN orders ON u.id = o.user_id"
2914        );
2915    }
2916
2917    #[test]
2918    fn test_build_returns_expression() {
2919        let expr = select(["id"]).from("users").build();
2920        assert!(matches!(expr, Expression::Select(_)));
2921    }
2922
2923    #[test]
2924    fn test_expr_interop() {
2925        // Can use Expr in select list
2926        let age_check = col("age").gt(lit(18));
2927        let sql = select([col("id"), age_check.alias("is_adult")])
2928            .from("users")
2929            .to_sql();
2930        assert_eq!(sql, "SELECT id, age > 18 AS is_adult FROM users");
2931    }
2932
2933    // -- Step 2: sql_expr / condition tests --
2934
2935    #[test]
2936    fn test_sql_expr_simple() {
2937        let expr = sql_expr("age > 18");
2938        let sql = select(["id"]).from("users").where_(expr).to_sql();
2939        assert_eq!(sql, "SELECT id FROM users WHERE age > 18");
2940    }
2941
2942    #[test]
2943    fn test_sql_expr_compound() {
2944        let expr = sql_expr("a > 1 AND b < 10");
2945        let sql = select(["*"]).from("t").where_(expr).to_sql();
2946        assert_eq!(sql, "SELECT * FROM t WHERE a > 1 AND b < 10");
2947    }
2948
2949    #[test]
2950    fn test_sql_expr_function() {
2951        let expr = sql_expr("COALESCE(a, b, 0)");
2952        let sql = select([expr.alias("val")]).from("t").to_sql();
2953        assert_eq!(sql, "SELECT COALESCE(a, b, 0) AS val FROM t");
2954    }
2955
2956    #[test]
2957    fn test_condition_alias() {
2958        let cond = condition("x > 0");
2959        let sql = select(["*"]).from("t").where_(cond).to_sql();
2960        assert_eq!(sql, "SELECT * FROM t WHERE x > 0");
2961    }
2962
2963    // -- Step 3: ilike, rlike, not_in tests --
2964
2965    #[test]
2966    fn test_ilike() {
2967        let sql = select(["id"])
2968            .from("users")
2969            .where_(col("name").ilike(lit("%test%")))
2970            .to_sql();
2971        assert_eq!(sql, "SELECT id FROM users WHERE name ILIKE '%test%'");
2972    }
2973
2974    #[test]
2975    fn test_rlike() {
2976        let sql = select(["id"])
2977            .from("users")
2978            .where_(col("name").rlike(lit("^[A-Z]")))
2979            .to_sql();
2980        assert_eq!(
2981            sql,
2982            "SELECT id FROM users WHERE REGEXP_LIKE(name, '^[A-Z]')"
2983        );
2984    }
2985
2986    #[test]
2987    fn test_not_in() {
2988        let sql = select(["id"])
2989            .from("users")
2990            .where_(col("status").not_in([lit("deleted"), lit("banned")]))
2991            .to_sql();
2992        assert_eq!(
2993            sql,
2994            "SELECT id FROM users WHERE NOT status IN ('deleted', 'banned')"
2995        );
2996    }
2997
2998    // -- Step 4: CaseBuilder tests --
2999
3000    #[test]
3001    fn test_case_searched() {
3002        let expr = case()
3003            .when(col("x").gt(lit(0)), lit("positive"))
3004            .when(col("x").eq(lit(0)), lit("zero"))
3005            .else_(lit("negative"))
3006            .build();
3007        let sql = select([expr.alias("label")]).from("t").to_sql();
3008        assert_eq!(
3009            sql,
3010            "SELECT CASE WHEN x > 0 THEN 'positive' WHEN x = 0 THEN 'zero' ELSE 'negative' END AS label FROM t"
3011        );
3012    }
3013
3014    #[test]
3015    fn test_case_simple() {
3016        let expr = case_of(col("status"))
3017            .when(lit(1), lit("active"))
3018            .when(lit(0), lit("inactive"))
3019            .build();
3020        let sql = select([expr.alias("status_label")]).from("t").to_sql();
3021        assert_eq!(
3022            sql,
3023            "SELECT CASE status WHEN 1 THEN 'active' WHEN 0 THEN 'inactive' END AS status_label FROM t"
3024        );
3025    }
3026
3027    #[test]
3028    fn test_case_no_else() {
3029        let expr = case().when(col("x").gt(lit(0)), lit("yes")).build();
3030        let sql = select([expr]).from("t").to_sql();
3031        assert_eq!(sql, "SELECT CASE WHEN x > 0 THEN 'yes' END FROM t");
3032    }
3033
3034    // -- Step 5: subquery tests --
3035
3036    #[test]
3037    fn test_subquery_in_from() {
3038        let inner = select(["id", "name"])
3039            .from("users")
3040            .where_(col("active").eq(boolean(true)));
3041        let outer = select(["sub.id"])
3042            .from_expr(subquery(inner, "sub"))
3043            .to_sql();
3044        assert_eq!(
3045            outer,
3046            "SELECT sub.id FROM (SELECT id, name FROM users WHERE active = TRUE) AS sub"
3047        );
3048    }
3049
3050    #[test]
3051    fn test_subquery_in_join() {
3052        let inner = select([col("user_id"), func("SUM", [col("amount")]).alias("total")])
3053            .from("orders")
3054            .group_by(["user_id"]);
3055        let sql = select(["u.name", "o.total"])
3056            .from("users")
3057            .join("orders", col("u.id").eq(col("o.user_id")))
3058            .to_sql();
3059        assert!(sql.contains("JOIN"));
3060        // Just verify the subquery builder doesn't panic
3061        let _sub = subquery(inner, "o");
3062    }
3063
3064    // -- Step 6: SetOpBuilder tests --
3065
3066    #[test]
3067    fn test_union() {
3068        let sql = union(select(["id"]).from("a"), select(["id"]).from("b")).to_sql();
3069        assert_eq!(sql, "SELECT id FROM a UNION SELECT id FROM b");
3070    }
3071
3072    #[test]
3073    fn test_union_all() {
3074        let sql = union_all(select(["id"]).from("a"), select(["id"]).from("b")).to_sql();
3075        assert_eq!(sql, "SELECT id FROM a UNION ALL SELECT id FROM b");
3076    }
3077
3078    #[test]
3079    fn test_intersect_builder() {
3080        let sql = intersect(select(["id"]).from("a"), select(["id"]).from("b")).to_sql();
3081        assert_eq!(sql, "SELECT id FROM a INTERSECT SELECT id FROM b");
3082    }
3083
3084    #[test]
3085    fn test_except_builder() {
3086        let sql = except_(select(["id"]).from("a"), select(["id"]).from("b")).to_sql();
3087        assert_eq!(sql, "SELECT id FROM a EXCEPT SELECT id FROM b");
3088    }
3089
3090    #[test]
3091    fn test_union_with_order_limit() {
3092        let sql = union(select(["id"]).from("a"), select(["id"]).from("b"))
3093            .order_by(["id"])
3094            .limit(10)
3095            .to_sql();
3096        assert!(sql.contains("UNION"));
3097        assert!(sql.contains("ORDER BY"));
3098        assert!(sql.contains("LIMIT"));
3099    }
3100
3101    #[test]
3102    fn test_select_builder_union() {
3103        let sql = select(["id"])
3104            .from("a")
3105            .union(select(["id"]).from("b"))
3106            .to_sql();
3107        assert_eq!(sql, "SELECT id FROM a UNION SELECT id FROM b");
3108    }
3109
3110    // -- Step 7: SelectBuilder extensions tests --
3111
3112    #[test]
3113    fn test_qualify() {
3114        let sql = select(["id", "name"])
3115            .from("users")
3116            .qualify(col("rn").eq(lit(1)))
3117            .to_sql();
3118        assert_eq!(sql, "SELECT id, name FROM users QUALIFY rn = 1");
3119    }
3120
3121    #[test]
3122    fn test_right_join() {
3123        let sql = select(["u.id", "o.amount"])
3124            .from("users")
3125            .right_join("orders", col("u.id").eq(col("o.user_id")))
3126            .to_sql();
3127        assert_eq!(
3128            sql,
3129            "SELECT u.id, o.amount FROM users RIGHT JOIN orders ON u.id = o.user_id"
3130        );
3131    }
3132
3133    #[test]
3134    fn test_cross_join() {
3135        let sql = select(["a.x", "b.y"]).from("a").cross_join("b").to_sql();
3136        assert_eq!(sql, "SELECT a.x, b.y FROM a CROSS JOIN b");
3137    }
3138
3139    #[test]
3140    fn test_lateral_view() {
3141        let sql = select(["id", "col_val"])
3142            .from("t")
3143            .lateral_view(func("EXPLODE", [col("arr")]), "lv", ["col_val"])
3144            .to_sql();
3145        assert!(sql.contains("LATERAL VIEW"));
3146        assert!(sql.contains("EXPLODE"));
3147    }
3148
3149    #[test]
3150    fn test_window_clause() {
3151        let sql = select(["id"])
3152            .from("t")
3153            .window(
3154                "w",
3155                WindowDefBuilder::new()
3156                    .partition_by(["dept"])
3157                    .order_by(["salary"]),
3158            )
3159            .to_sql();
3160        assert!(sql.contains("WINDOW"));
3161        assert!(sql.contains("PARTITION BY"));
3162    }
3163
3164    // -- XOR operator tests --
3165
3166    #[test]
3167    fn test_xor() {
3168        let sql = select(["*"])
3169            .from("t")
3170            .where_(col("a").xor(col("b")))
3171            .to_sql();
3172        assert_eq!(sql, "SELECT * FROM t WHERE a XOR b");
3173    }
3174
3175    // -- FOR UPDATE / FOR SHARE tests --
3176
3177    #[test]
3178    fn test_for_update() {
3179        let sql = select(["id"]).from("t").for_update().to_sql();
3180        assert_eq!(sql, "SELECT id FROM t FOR UPDATE");
3181    }
3182
3183    #[test]
3184    fn test_for_share() {
3185        let sql = select(["id"]).from("t").for_share().to_sql();
3186        assert_eq!(sql, "SELECT id FROM t FOR SHARE");
3187    }
3188
3189    // -- Hint tests --
3190
3191    #[test]
3192    fn test_hint() {
3193        let sql = select(["*"]).from("t").hint("FULL(t)").to_sql();
3194        assert!(sql.contains("FULL(t)"), "Expected hint in: {}", sql);
3195    }
3196
3197    // -- CTAS tests --
3198
3199    #[test]
3200    fn test_ctas() {
3201        let expr = select(["*"]).from("t").ctas("new_table");
3202        let sql = Generator::sql(&expr).unwrap();
3203        assert_eq!(sql, "CREATE TABLE new_table AS SELECT * FROM t");
3204    }
3205
3206    // -- MergeBuilder tests --
3207
3208    #[test]
3209    fn test_merge_update_insert() {
3210        let sql = merge_into("target")
3211            .using("source", col("target.id").eq(col("source.id")))
3212            .when_matched_update(vec![("name", col("source.name"))])
3213            .when_not_matched_insert(&["id", "name"], vec![col("source.id"), col("source.name")])
3214            .to_sql();
3215        assert!(
3216            sql.contains("MERGE INTO"),
3217            "Expected MERGE INTO in: {}",
3218            sql
3219        );
3220        assert!(sql.contains("USING"), "Expected USING in: {}", sql);
3221        assert!(
3222            sql.contains("WHEN MATCHED"),
3223            "Expected WHEN MATCHED in: {}",
3224            sql
3225        );
3226        assert!(
3227            sql.contains("UPDATE SET"),
3228            "Expected UPDATE SET in: {}",
3229            sql
3230        );
3231        assert!(
3232            sql.contains("WHEN NOT MATCHED"),
3233            "Expected WHEN NOT MATCHED in: {}",
3234            sql
3235        );
3236        assert!(sql.contains("INSERT"), "Expected INSERT in: {}", sql);
3237    }
3238
3239    #[test]
3240    fn test_merge_delete() {
3241        let sql = merge_into("target")
3242            .using("source", col("target.id").eq(col("source.id")))
3243            .when_matched_delete()
3244            .to_sql();
3245        assert!(
3246            sql.contains("MERGE INTO"),
3247            "Expected MERGE INTO in: {}",
3248            sql
3249        );
3250        assert!(
3251            sql.contains("WHEN MATCHED THEN DELETE"),
3252            "Expected WHEN MATCHED THEN DELETE in: {}",
3253            sql
3254        );
3255    }
3256
3257    #[test]
3258    fn test_merge_with_condition() {
3259        let sql = merge_into("target")
3260            .using("source", col("target.id").eq(col("source.id")))
3261            .when_matched_update_where(
3262                col("source.active").eq(boolean(true)),
3263                vec![("name", col("source.name"))],
3264            )
3265            .to_sql();
3266        assert!(
3267            sql.contains("MERGE INTO"),
3268            "Expected MERGE INTO in: {}",
3269            sql
3270        );
3271        assert!(
3272            sql.contains("AND source.active = TRUE"),
3273            "Expected condition in: {}",
3274            sql
3275        );
3276    }
3277}