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