Skip to main content

polyglot_sql/
builder.rs

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