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