Skip to main content

sqlglot_rust/generator/
sql_generator.rs

1use crate::ast::*;
2use crate::dialects::Dialect;
3
4/// SQL code generator that converts an AST into a SQL string.
5///
6/// Supports all statement and expression types defined in the AST,
7/// including CTEs, subqueries, UNION/INTERSECT/EXCEPT, CAST, window
8/// functions, EXISTS, EXTRACT, INTERVAL, and more.
9pub struct Generator {
10    output: String,
11    /// When true, emit formatted SQL with indentation and newlines.
12    pretty: bool,
13    indent: usize,
14    /// Target dialect for dialect-specific generation (typed functions).
15    dialect: Option<Dialect>,
16}
17
18impl Generator {
19    #[must_use]
20    pub fn new() -> Self {
21        Self {
22            output: String::new(),
23            pretty: false,
24            indent: 0,
25            dialect: None,
26        }
27    }
28
29    /// Create a generator that produces formatted SQL.
30    #[must_use]
31    pub fn pretty() -> Self {
32        Self {
33            output: String::new(),
34            pretty: true,
35            indent: 0,
36            dialect: None,
37        }
38    }
39
40    /// Create a generator targeting a specific dialect.
41    #[must_use]
42    pub fn with_dialect(dialect: Dialect) -> Self {
43        Self {
44            output: String::new(),
45            pretty: false,
46            indent: 0,
47            dialect: Some(dialect),
48        }
49    }
50
51    /// Generate SQL from a statement.
52    #[must_use]
53    pub fn generate(&mut self, statement: &Statement) -> String {
54        self.output.clear();
55        self.gen_statement(statement);
56        self.output.clone()
57    }
58
59    /// Generate SQL for an expression (static helper for `Expr::sql()`).
60    #[must_use]
61    pub fn expr_to_sql(expr: &Expr) -> String {
62        let mut g = Self::new();
63        g.gen_expr(expr);
64        g.output
65    }
66
67    fn write(&mut self, s: &str) {
68        self.output.push_str(s);
69    }
70
71    /// Emit a newline followed by current indentation (pretty mode only).
72    fn newline(&mut self) {
73        if self.pretty {
74            self.output.push('\n');
75            for _ in 0..self.indent {
76                self.output.push_str("  ");
77            }
78        }
79    }
80
81    /// In pretty mode: newline + indent. In compact mode: a single space.
82    fn sep(&mut self) {
83        if self.pretty {
84            self.newline();
85        } else {
86            self.output.push(' ');
87        }
88    }
89
90    fn indent_up(&mut self) {
91        self.indent += 1;
92    }
93
94    fn indent_down(&mut self) {
95        self.indent = self.indent.saturating_sub(1);
96    }
97
98    fn write_keyword(&mut self, s: &str) {
99        self.write(s);
100    }
101
102    /// Emit a column/table alias, automatically wrapping it in the target
103    /// dialect's canonical quoting style when the alias collides with a
104    /// reserved keyword for that dialect. Preserves any explicit quoting
105    /// the source already specified.
106    fn write_alias(&mut self, alias: &str, style: QuoteStyle) {
107        let effective = if !style.is_quoted()
108            && matches!(self.dialect, Some(d) if crate::dialects::is_tsql_family(d))
109            && crate::dialects::is_tsql_reserved(alias)
110        {
111            QuoteStyle::Bracket
112        } else {
113            style
114        };
115        self.write_quoted(alias, effective);
116    }
117
118    /// Write an identifier with the given quoting style.
119    /// If a target dialect is set and the identifier is quoted, the quoting
120    /// is transformed to the target dialect's canonical style.
121    fn write_quoted(&mut self, name: &str, style: QuoteStyle) {
122        let effective_style = if style.is_quoted() {
123            if let Some(dialect) = self.dialect {
124                QuoteStyle::for_dialect(dialect)
125            } else {
126                style
127            }
128        } else {
129            style
130        };
131        match effective_style {
132            QuoteStyle::None => self.write(name),
133            QuoteStyle::DoubleQuote => {
134                self.write("\"");
135                self.write(&name.replace('"', "\"\""));
136                self.write("\"");
137            }
138            QuoteStyle::Backtick => {
139                self.write("`");
140                self.write(&name.replace('`', "``"));
141                self.write("`");
142            }
143            QuoteStyle::Bracket => {
144                self.write("[");
145                self.write(&name.replace(']', "]]"));
146                self.write("]");
147            }
148        }
149    }
150
151    // ══════════════════════════════════════════════════════════════
152    // Statements
153    // ══════════════════════════════════════════════════════════════
154
155    /// Emit comment strings, each on its own line.
156    /// MySQL hash comments (`#`) are converted to standard `--` comments
157    /// when the target dialect is not MySQL.
158    fn gen_comments(&mut self, comments: &[String]) {
159        for comment in comments {
160            let normalized = self.normalize_comment(comment);
161            self.write(&normalized);
162            self.newline_or_space();
163        }
164    }
165
166    /// Normalize a comment for the target dialect.
167    /// Converts MySQL `#` comments to `--` when targeting non-MySQL dialects.
168    fn normalize_comment(&self, comment: &str) -> String {
169        if comment.starts_with('#') {
170            let is_mysql_target = matches!(
171                self.dialect,
172                Some(Dialect::Mysql | Dialect::Doris | Dialect::SingleStore | Dialect::StarRocks)
173            );
174            if !is_mysql_target {
175                return format!("--{}", &comment[1..]);
176            }
177        }
178        comment.to_string()
179    }
180
181    /// Emit a newline in pretty mode or a space in compact mode.
182    fn newline_or_space(&mut self) {
183        if self.pretty {
184            self.output.push('\n');
185            for _ in 0..self.indent {
186                self.output.push_str("  ");
187            }
188        } else {
189            self.output.push('\n');
190        }
191    }
192
193    fn gen_statement(&mut self, statement: &Statement) {
194        // Emit leading comments for statements that carry them.
195        match statement {
196            Statement::Select(s) => {
197                self.gen_comments(&s.comments);
198                self.gen_select(s);
199            }
200            Statement::Insert(s) => {
201                self.gen_comments(&s.comments);
202                self.gen_insert(s);
203            }
204            Statement::Update(s) => {
205                self.gen_comments(&s.comments);
206                self.gen_update(s);
207            }
208            Statement::Delete(s) => {
209                self.gen_comments(&s.comments);
210                self.gen_delete(s);
211            }
212            Statement::CreateTable(s) => {
213                self.gen_comments(&s.comments);
214                self.gen_create_table(s);
215            }
216            Statement::DropTable(s) => {
217                self.gen_comments(&s.comments);
218                self.gen_drop_table(s);
219            }
220            Statement::SetOperation(s) => {
221                self.gen_comments(&s.comments);
222                self.gen_set_operation(s);
223            }
224            Statement::AlterTable(s) => {
225                self.gen_comments(&s.comments);
226                self.gen_alter_table(s);
227            }
228            Statement::CreateView(s) => {
229                self.gen_comments(&s.comments);
230                self.gen_create_view(s);
231            }
232            Statement::DropView(s) => {
233                self.gen_comments(&s.comments);
234                self.gen_drop_view(s);
235            }
236            Statement::Truncate(s) => {
237                self.gen_comments(&s.comments);
238                self.gen_truncate(s);
239            }
240            Statement::Transaction(s) => self.gen_transaction(s),
241            Statement::Explain(s) => {
242                self.gen_comments(&s.comments);
243                self.gen_explain(s);
244            }
245            Statement::Use(s) => {
246                self.gen_comments(&s.comments);
247                self.gen_use(s);
248            }
249            Statement::Merge(s) => {
250                self.gen_comments(&s.comments);
251                self.gen_merge(s);
252            }
253            Statement::Expression(e) => self.gen_expr(e),
254            Statement::Command(s) => {
255                self.gen_comments(&s.comments);
256                self.gen_command(s);
257            }
258        }
259    }
260
261    fn gen_command(&mut self, c: &crate::ast::CommandStatement) {
262        self.write(&c.kind);
263        if !c.body.is_empty() {
264            self.write(" ");
265            self.write(&c.body);
266        }
267    }
268
269    // ── SELECT ──────────────────────────────────────────────────
270
271    fn gen_select(&mut self, sel: &SelectStatement) {
272        // CTEs
273        if !sel.ctes.is_empty() {
274            self.gen_ctes(&sel.ctes);
275            self.sep();
276        }
277
278        self.write_keyword("SELECT");
279        if sel.distinct {
280            self.write(" ");
281            self.write_keyword("DISTINCT");
282        }
283        if let Some(top) = &sel.top {
284            self.write(" ");
285            self.write_keyword("TOP ");
286            self.gen_expr(top);
287        }
288
289        // columns
290        if self.pretty {
291            self.indent_up();
292            for (i, item) in sel.columns.iter().enumerate() {
293                self.newline();
294                self.gen_select_item(item);
295                if i < sel.columns.len() - 1 {
296                    self.write(",");
297                }
298            }
299            self.indent_down();
300        } else {
301            self.write(" ");
302            for (i, item) in sel.columns.iter().enumerate() {
303                if i > 0 {
304                    self.write(", ");
305                }
306                self.gen_select_item(item);
307            }
308        }
309
310        if let Some(from) = &sel.from {
311            self.sep();
312            self.write_keyword("FROM");
313            if self.pretty {
314                self.indent_up();
315                self.newline();
316                self.gen_table_source(&from.source);
317                self.indent_down();
318            } else {
319                self.write(" ");
320                self.gen_table_source(&from.source);
321            }
322        } else if matches!(self.dialect, Some(Dialect::Oracle)) {
323            // Oracle 21c and earlier reject a FROM-less SELECT (ORA-00923). Emit
324            // `FROM DUAL` for portability — valid on every Oracle version. All
325            // FROM-less selects funnel through here (top-level statements, scalar
326            // subqueries, EXISTS, derived tables, and each set-operation branch),
327            // so nested cases are covered too. (PSQ-2848)
328            self.sep();
329            self.write_keyword("FROM DUAL");
330        }
331
332        for join in &sel.joins {
333            self.gen_join(join);
334        }
335
336        if let Some(wh) = &sel.where_clause {
337            self.sep();
338            self.write_keyword("WHERE");
339            if self.pretty {
340                self.indent_up();
341                self.newline();
342                self.gen_condition(wh);
343                self.indent_down();
344            } else {
345                self.write(" ");
346                self.gen_condition(wh);
347            }
348        }
349
350        if !sel.group_by.is_empty() {
351            self.sep();
352            self.write_keyword("GROUP BY");
353            if self.pretty {
354                self.indent_up();
355                self.newline();
356                self.gen_expr_list(&sel.group_by);
357                self.indent_down();
358            } else {
359                self.write(" ");
360                self.gen_expr_list(&sel.group_by);
361            }
362        }
363
364        if let Some(having) = &sel.having {
365            self.sep();
366            self.write_keyword("HAVING");
367            if self.pretty {
368                self.indent_up();
369                self.newline();
370                self.gen_condition(having);
371                self.indent_down();
372            } else {
373                self.write(" ");
374                self.gen_condition(having);
375            }
376        }
377
378        if let Some(qualify) = &sel.qualify {
379            self.sep();
380            self.write_keyword("QUALIFY");
381            if self.pretty {
382                self.indent_up();
383                self.newline();
384                self.gen_condition(qualify);
385                self.indent_down();
386            } else {
387                self.write(" ");
388                self.gen_condition(qualify);
389            }
390        }
391
392        if !sel.window_definitions.is_empty() {
393            self.sep();
394            self.write_keyword("WINDOW ");
395            for (i, wd) in sel.window_definitions.iter().enumerate() {
396                if i > 0 {
397                    self.write(", ");
398                }
399                self.write(&wd.name);
400                self.write(" AS (");
401                self.gen_window_spec(&wd.spec);
402                self.write(")");
403            }
404        }
405
406        self.gen_order_by(&sel.order_by);
407
408        if let Some(limit) = &sel.limit {
409            self.sep();
410            self.write_keyword("LIMIT ");
411            self.gen_expr(limit);
412        }
413
414        if let Some(offset) = &sel.offset {
415            self.sep();
416            if matches!(
417                self.dialect,
418                Some(Dialect::Tsql) | Some(Dialect::Fabric) | Some(Dialect::Oracle)
419            ) {
420                self.write_keyword("OFFSET ");
421                self.gen_expr(offset);
422                self.write(" ");
423                self.write_keyword("ROWS");
424            } else {
425                self.write_keyword("OFFSET ");
426                self.gen_expr(offset);
427            }
428        }
429
430        if let Some(fetch) = &sel.fetch_first {
431            self.sep();
432            if matches!(self.dialect, Some(Dialect::Tsql) | Some(Dialect::Fabric)) {
433                self.write_keyword("FETCH NEXT ");
434                self.gen_expr(fetch);
435                self.write(" ");
436                self.write_keyword("ROWS ONLY");
437            } else {
438                self.write_keyword("FETCH FIRST ");
439                self.gen_expr(fetch);
440                self.write(" ");
441                self.write_keyword("ROWS ONLY");
442            }
443        }
444    }
445
446    fn gen_ctes(&mut self, ctes: &[Cte]) {
447        self.write_keyword("WITH ");
448        if ctes.iter().any(|c| c.recursive) {
449            self.write_keyword("RECURSIVE ");
450        }
451        for (i, cte) in ctes.iter().enumerate() {
452            if i > 0 {
453                self.write(",");
454                self.sep();
455            }
456            self.write_quoted(&cte.name, cte.name_quote_style);
457            if !cte.columns.is_empty() {
458                self.write("(");
459                self.write(&cte.columns.join(", "));
460                self.write(")");
461            }
462            self.write(" ");
463            self.write_keyword("AS ");
464            if let Some(true) = cte.materialized {
465                self.write_keyword("MATERIALIZED ");
466            } else if let Some(false) = cte.materialized {
467                self.write_keyword("NOT MATERIALIZED ");
468            }
469            self.write("(");
470            if self.pretty {
471                self.indent_up();
472                self.newline();
473                self.gen_statement(&cte.query);
474                self.indent_down();
475                self.newline();
476            } else {
477                self.gen_statement(&cte.query);
478            }
479            self.write(")");
480        }
481    }
482
483    fn gen_select_item(&mut self, item: &SelectItem) {
484        match item {
485            SelectItem::Wildcard => self.write("*"),
486            SelectItem::QualifiedWildcard { table } => {
487                self.write(table);
488                self.write(".*");
489            }
490            SelectItem::Expr {
491                expr,
492                alias,
493                alias_quote_style,
494            } => {
495                self.gen_expr(expr);
496                if let Some(alias) = alias {
497                    self.write(" ");
498                    self.write_keyword("AS ");
499                    self.write_alias(alias, *alias_quote_style);
500                }
501            }
502        }
503    }
504
505    fn gen_table_source(&mut self, source: &TableSource) {
506        match source {
507            TableSource::Table(table_ref) => self.gen_table_ref(table_ref),
508            TableSource::Subquery {
509                query,
510                alias,
511                alias_quote_style,
512            } => {
513                self.write("(");
514                self.gen_statement(query);
515                self.write(")");
516                if let Some(alias) = alias {
517                    self.write(" ");
518                    if !self.omit_table_alias_as() {
519                        self.write_keyword("AS ");
520                    }
521                    self.write_alias(alias, *alias_quote_style);
522                }
523            }
524            TableSource::TableFunction {
525                name,
526                args,
527                alias,
528                alias_quote_style,
529            } => {
530                self.write(name);
531                self.write("(");
532                self.gen_expr_list(args);
533                self.write(")");
534                if let Some(alias) = alias {
535                    self.write(" ");
536                    if !self.omit_table_alias_as() {
537                        self.write_keyword("AS ");
538                    }
539                    self.write_alias(alias, *alias_quote_style);
540                }
541            }
542            TableSource::Lateral { source } => {
543                self.write_keyword("LATERAL ");
544                self.gen_table_source(source);
545            }
546            TableSource::Unnest {
547                expr,
548                alias,
549                alias_quote_style,
550                with_offset,
551            } => {
552                self.write_keyword("UNNEST(");
553                self.gen_expr(expr);
554                self.write(")");
555                if let Some(alias) = alias {
556                    self.write(" ");
557                    if !self.omit_table_alias_as() {
558                        self.write_keyword("AS ");
559                    }
560                    self.write_quoted(alias, *alias_quote_style);
561                }
562                if *with_offset {
563                    self.write(" ");
564                    self.write_keyword("WITH OFFSET");
565                }
566            }
567            TableSource::Pivot {
568                source,
569                aggregate,
570                for_column,
571                in_values,
572                alias,
573                alias_quote_style,
574            } => {
575                self.gen_table_source(source);
576                self.write(" ");
577                self.write_keyword("PIVOT");
578                self.write(" (");
579                self.gen_expr(aggregate);
580                self.write(" ");
581                self.write_keyword("FOR ");
582                self.write(for_column);
583                self.write(" ");
584                self.write_keyword("IN ");
585                self.write("(");
586                self.gen_pivot_values(in_values);
587                self.write("))");
588                if let Some(alias) = alias {
589                    self.write(" ");
590                    if !self.omit_table_alias_as() {
591                        self.write_keyword("AS ");
592                    }
593                    self.write_quoted(alias, *alias_quote_style);
594                }
595            }
596            TableSource::Unpivot {
597                source,
598                value_column,
599                for_column,
600                in_columns,
601                alias,
602                alias_quote_style,
603            } => {
604                self.gen_table_source(source);
605                self.write(" ");
606                self.write_keyword("UNPIVOT");
607                self.write(" (");
608                self.write(value_column);
609                self.write(" ");
610                self.write_keyword("FOR ");
611                self.write(for_column);
612                self.write(" ");
613                self.write_keyword("IN ");
614                self.write("(");
615                self.gen_pivot_values(in_columns);
616                self.write("))");
617                if let Some(alias) = alias {
618                    self.write(" ");
619                    if !self.omit_table_alias_as() {
620                        self.write_keyword("AS ");
621                    }
622                    self.write_quoted(alias, *alias_quote_style);
623                }
624            }
625        }
626    }
627
628    fn gen_pivot_values(&mut self, values: &[PivotValue]) {
629        for (i, pv) in values.iter().enumerate() {
630            if i > 0 {
631                self.write(", ");
632            }
633            self.gen_expr(&pv.value);
634            if let Some(alias) = &pv.alias {
635                self.write(" ");
636                self.write_keyword("AS ");
637                self.write_alias(alias, pv.alias_quote_style);
638            }
639        }
640    }
641
642    /// Returns true if the target dialect forbids `AS` in table aliases.
643    fn omit_table_alias_as(&self) -> bool {
644        matches!(self.dialect, Some(Dialect::Oracle))
645    }
646
647    fn gen_table_ref(&mut self, table: &TableRef) {
648        if let Some(catalog) = &table.catalog {
649            self.write(catalog);
650            self.write(".");
651        }
652        if let Some(schema) = &table.schema {
653            self.write(schema);
654            self.write(".");
655        }
656        self.write_quoted(&table.name, table.name_quote_style);
657        if let Some(alias) = &table.alias {
658            self.write(" ");
659            if !self.omit_table_alias_as() {
660                self.write_keyword("AS ");
661            }
662            self.write_alias(alias, table.alias_quote_style);
663        }
664    }
665
666    fn gen_join(&mut self, join: &JoinClause) {
667        let join_kw = match join.join_type {
668            JoinType::Inner => "INNER JOIN",
669            JoinType::Left => "LEFT JOIN",
670            JoinType::Right => "RIGHT JOIN",
671            JoinType::Full => "FULL JOIN",
672            JoinType::Cross => "CROSS JOIN",
673            JoinType::Natural => "NATURAL JOIN",
674            JoinType::Lateral => "LATERAL JOIN",
675        };
676        self.sep();
677        self.write_keyword(join_kw);
678        if self.pretty {
679            self.indent_up();
680            self.newline();
681            self.gen_table_source(&join.table);
682        } else {
683            self.write(" ");
684            self.gen_table_source(&join.table);
685        }
686        if let Some(on) = &join.on {
687            if self.pretty {
688                self.newline();
689            } else {
690                self.write(" ");
691            }
692            self.write_keyword("ON ");
693            self.gen_condition(on);
694        }
695        if !join.using.is_empty() {
696            if self.pretty {
697                self.newline();
698            } else {
699                self.write(" ");
700            }
701            self.write_keyword("USING (");
702            self.write(&join.using.join(", "));
703            self.write(")");
704        }
705        if self.pretty {
706            self.indent_down();
707        }
708    }
709
710    fn gen_order_by_items_inline(&mut self, items: &[OrderByItem]) {
711        for (i, item) in items.iter().enumerate() {
712            if i > 0 {
713                self.write(", ");
714            }
715            self.gen_expr(&item.expr);
716            if !item.ascending {
717                self.write(" ");
718                self.write_keyword("DESC");
719            }
720            if let Some(nulls_first) = item.nulls_first {
721                if nulls_first {
722                    self.write(" ");
723                    self.write_keyword("NULLS FIRST");
724                } else {
725                    self.write(" ");
726                    self.write_keyword("NULLS LAST");
727                }
728            }
729        }
730    }
731
732    fn gen_order_by(&mut self, items: &[OrderByItem]) {
733        if items.is_empty() {
734            return;
735        }
736        self.sep();
737        self.write_keyword("ORDER BY");
738        if self.pretty {
739            self.indent_up();
740            self.newline();
741        } else {
742            self.write(" ");
743        }
744        for (i, item) in items.iter().enumerate() {
745            if i > 0 {
746                self.write(", ");
747            }
748            self.gen_expr(&item.expr);
749            if !item.ascending {
750                self.write(" ");
751                self.write_keyword("DESC");
752            }
753            if let Some(nulls_first) = item.nulls_first {
754                if nulls_first {
755                    self.write(" ");
756                    self.write_keyword("NULLS FIRST");
757                } else {
758                    self.write(" ");
759                    self.write_keyword("NULLS LAST");
760                }
761            }
762        }
763        if self.pretty {
764            self.indent_down();
765        }
766    }
767
768    // ── Set operations ──────────────────────────────────────────
769
770    fn gen_set_operation(&mut self, sop: &SetOperationStatement) {
771        self.gen_statement(&sop.left);
772        let op_kw = match sop.op {
773            SetOperationType::Union => "UNION",
774            SetOperationType::Intersect => "INTERSECT",
775            SetOperationType::Except => "EXCEPT",
776        };
777        self.sep();
778        self.write_keyword(op_kw);
779        if sop.all {
780            self.write(" ");
781            self.write_keyword("ALL");
782        }
783        self.sep();
784        self.gen_statement(&sop.right);
785
786        self.gen_order_by(&sop.order_by);
787
788        if let Some(limit) = &sop.limit {
789            self.sep();
790            self.write_keyword("LIMIT ");
791            self.gen_expr(limit);
792        }
793        if let Some(offset) = &sop.offset {
794            self.sep();
795            self.write_keyword("OFFSET ");
796            self.gen_expr(offset);
797        }
798    }
799
800    // ── INSERT ──────────────────────────────────────────────────
801
802    fn gen_insert(&mut self, ins: &InsertStatement) {
803        self.write_keyword("INSERT INTO ");
804        self.gen_table_ref(&ins.table);
805
806        if !ins.columns.is_empty() {
807            self.write(" (");
808            self.write(&ins.columns.join(", "));
809            self.write(")");
810        }
811
812        // T-SQL: OUTPUT goes before VALUES
813        if matches!(self.dialect, Some(Dialect::Tsql) | Some(Dialect::Fabric))
814            && !ins.returning.is_empty()
815        {
816            self.sep();
817            self.gen_output_clause(&ins.returning, "INSERTED");
818        }
819
820        match &ins.source {
821            InsertSource::Values(rows) => {
822                self.sep();
823                self.write_keyword("VALUES");
824                if self.pretty {
825                    self.indent_up();
826                    for (i, row) in rows.iter().enumerate() {
827                        self.newline();
828                        self.write("(");
829                        self.gen_expr_list(row);
830                        self.write(")");
831                        if i < rows.len() - 1 {
832                            self.write(",");
833                        }
834                    }
835                    self.indent_down();
836                } else {
837                    self.write(" ");
838                    for (i, row) in rows.iter().enumerate() {
839                        if i > 0 {
840                            self.write(", ");
841                        }
842                        self.write("(");
843                        self.gen_expr_list(row);
844                        self.write(")");
845                    }
846                }
847            }
848            InsertSource::Query(query) => {
849                self.sep();
850                self.gen_statement(query);
851            }
852            InsertSource::Default => {
853                self.sep();
854                self.write_keyword("DEFAULT VALUES");
855            }
856        }
857
858        if let Some(on_conflict) = &ins.on_conflict {
859            self.sep();
860            self.write_keyword("ON CONFLICT");
861            if !on_conflict.columns.is_empty() {
862                self.write(" (");
863                self.write(&on_conflict.columns.join(", "));
864                self.write(")");
865            }
866            match &on_conflict.action {
867                ConflictAction::DoNothing => {
868                    self.write(" ");
869                    self.write_keyword("DO NOTHING");
870                }
871                ConflictAction::DoUpdate(assignments) => {
872                    self.write(" ");
873                    self.write_keyword("DO UPDATE SET ");
874                    for (i, (col, val)) in assignments.iter().enumerate() {
875                        if i > 0 {
876                            self.write(", ");
877                        }
878                        self.write(col);
879                        self.write(" = ");
880                        self.gen_expr(val);
881                    }
882                }
883            }
884        }
885
886        // Non-T-SQL: RETURNING goes after VALUES
887        if !matches!(self.dialect, Some(Dialect::Tsql) | Some(Dialect::Fabric))
888            && !ins.returning.is_empty()
889        {
890            self.sep();
891            self.write_keyword("RETURNING ");
892            for (i, item) in ins.returning.iter().enumerate() {
893                if i > 0 {
894                    self.write(", ");
895                }
896                self.gen_select_item(item);
897            }
898        }
899    }
900
901    // ── UPDATE ──────────────────────────────────────────────────
902
903    fn gen_update(&mut self, upd: &UpdateStatement) {
904        self.write_keyword("UPDATE ");
905        self.gen_table_ref(&upd.table);
906        self.sep();
907        self.write_keyword("SET");
908
909        if self.pretty {
910            self.indent_up();
911            for (i, (col, val)) in upd.assignments.iter().enumerate() {
912                self.newline();
913                self.write(col);
914                self.write(" = ");
915                self.gen_expr(val);
916                if i < upd.assignments.len() - 1 {
917                    self.write(",");
918                }
919            }
920            self.indent_down();
921        } else {
922            self.write(" ");
923            for (i, (col, val)) in upd.assignments.iter().enumerate() {
924                if i > 0 {
925                    self.write(", ");
926                }
927                self.write(col);
928                self.write(" = ");
929                self.gen_expr(val);
930            }
931        }
932
933        if let Some(from) = &upd.from {
934            self.sep();
935            self.write_keyword("FROM ");
936            self.gen_table_source(&from.source);
937        }
938
939        if let Some(wh) = &upd.where_clause {
940            self.sep();
941            self.write_keyword("WHERE");
942            if self.pretty {
943                self.indent_up();
944                self.newline();
945                self.gen_condition(wh);
946                self.indent_down();
947            } else {
948                self.write(" ");
949                self.gen_condition(wh);
950            }
951        }
952
953        if !upd.returning.is_empty() {
954            self.sep();
955            if matches!(self.dialect, Some(Dialect::Tsql) | Some(Dialect::Fabric)) {
956                self.gen_output_clause(&upd.returning, "INSERTED");
957            } else {
958                self.write_keyword("RETURNING ");
959                for (i, item) in upd.returning.iter().enumerate() {
960                    if i > 0 {
961                        self.write(", ");
962                    }
963                    self.gen_select_item(item);
964                }
965            }
966        }
967    }
968
969    // ── DELETE ──────────────────────────────────────────────────
970
971    fn gen_delete(&mut self, del: &DeleteStatement) {
972        self.write_keyword("DELETE FROM ");
973        self.gen_table_ref(&del.table);
974
975        if let Some(using) = &del.using {
976            self.sep();
977            self.write_keyword("USING ");
978            self.gen_table_source(&using.source);
979        }
980
981        // T-SQL: OUTPUT clause comes before WHERE
982        if !del.returning.is_empty()
983            && matches!(self.dialect, Some(Dialect::Tsql) | Some(Dialect::Fabric))
984        {
985            self.sep();
986            self.gen_output_clause(&del.returning, "DELETED");
987        }
988
989        if let Some(wh) = &del.where_clause {
990            self.sep();
991            self.write_keyword("WHERE");
992            if self.pretty {
993                self.indent_up();
994                self.newline();
995                self.gen_condition(wh);
996                self.indent_down();
997            } else {
998                self.write(" ");
999                self.gen_condition(wh);
1000            }
1001        }
1002
1003        // Non-T-SQL: RETURNING after WHERE
1004        if !del.returning.is_empty()
1005            && !matches!(self.dialect, Some(Dialect::Tsql) | Some(Dialect::Fabric))
1006        {
1007            self.sep();
1008            self.write_keyword("RETURNING ");
1009            for (i, item) in del.returning.iter().enumerate() {
1010                if i > 0 {
1011                    self.write(", ");
1012                }
1013                self.gen_select_item(item);
1014            }
1015        }
1016    }
1017
1018    // ── CREATE TABLE ────────────────────────────────────────────
1019
1020    /// Emit a T-SQL OUTPUT clause: `OUTPUT prefix.col1, prefix.col2`
1021    fn gen_output_clause(&mut self, items: &[SelectItem], prefix: &str) {
1022        self.write_keyword("OUTPUT ");
1023        for (i, item) in items.iter().enumerate() {
1024            if i > 0 {
1025                self.write(", ");
1026            }
1027            match item {
1028                SelectItem::Wildcard => {
1029                    self.write(prefix);
1030                    self.write(".*");
1031                }
1032                SelectItem::QualifiedWildcard { table } => {
1033                    self.write(prefix);
1034                    self.write(".");
1035                    self.write(table);
1036                    self.write(".*");
1037                }
1038                SelectItem::Expr { expr, .. } => match expr {
1039                    Expr::Column { name, .. } => {
1040                        self.write(prefix);
1041                        self.write(".");
1042                        self.write(name);
1043                    }
1044                    Expr::Star | Expr::Wildcard => {
1045                        self.write(prefix);
1046                        self.write(".*");
1047                    }
1048                    _ => {
1049                        self.write(prefix);
1050                        self.write(".");
1051                        self.gen_expr(expr);
1052                    }
1053                },
1054            }
1055        }
1056    }
1057
1058    // ── MERGE ───────────────────────────────────────────────────
1059
1060    fn gen_merge(&mut self, merge: &MergeStatement) {
1061        self.write_keyword("MERGE INTO ");
1062        self.gen_table_ref(&merge.target);
1063
1064        self.sep();
1065        self.write_keyword("USING ");
1066        self.gen_table_source(&merge.source);
1067
1068        self.sep();
1069        self.write_keyword("ON");
1070        if self.pretty {
1071            self.indent_up();
1072            self.newline();
1073            self.gen_expr(&merge.on);
1074            self.indent_down();
1075        } else {
1076            self.write(" ");
1077            self.gen_expr(&merge.on);
1078        }
1079
1080        for clause in &merge.clauses {
1081            self.sep();
1082            self.gen_merge_clause(clause);
1083        }
1084
1085        if !merge.output.is_empty() {
1086            self.sep();
1087            self.write_keyword("OUTPUT ");
1088            for (i, item) in merge.output.iter().enumerate() {
1089                if i > 0 {
1090                    self.write(", ");
1091                }
1092                self.gen_select_item(item);
1093            }
1094        }
1095    }
1096
1097    fn gen_merge_clause(&mut self, clause: &MergeClause) {
1098        self.write_keyword("WHEN ");
1099        match &clause.kind {
1100            MergeClauseKind::Matched => self.write_keyword("MATCHED"),
1101            MergeClauseKind::NotMatched => self.write_keyword("NOT MATCHED"),
1102            MergeClauseKind::NotMatchedBySource => self.write_keyword("NOT MATCHED BY SOURCE"),
1103        }
1104
1105        if let Some(cond) = &clause.condition {
1106            self.write_keyword(" AND ");
1107            self.gen_condition(cond);
1108        }
1109
1110        self.write_keyword(" THEN");
1111
1112        match &clause.action {
1113            MergeAction::Update(assignments) => {
1114                self.sep();
1115                self.write_keyword("UPDATE SET");
1116                if self.pretty {
1117                    self.indent_up();
1118                    for (i, (col, val)) in assignments.iter().enumerate() {
1119                        self.newline();
1120                        self.write(col);
1121                        self.write(" = ");
1122                        self.gen_expr(val);
1123                        if i < assignments.len() - 1 {
1124                            self.write(",");
1125                        }
1126                    }
1127                    self.indent_down();
1128                } else {
1129                    self.write(" ");
1130                    for (i, (col, val)) in assignments.iter().enumerate() {
1131                        if i > 0 {
1132                            self.write(", ");
1133                        }
1134                        self.write(col);
1135                        self.write(" = ");
1136                        self.gen_expr(val);
1137                    }
1138                }
1139            }
1140            MergeAction::Insert { columns, values } => {
1141                self.sep();
1142                self.write_keyword("INSERT");
1143                if !columns.is_empty() {
1144                    self.write(" (");
1145                    self.write(&columns.join(", "));
1146                    self.write(")");
1147                }
1148                self.write_keyword(" VALUES");
1149                self.write(" (");
1150                for (i, val) in values.iter().enumerate() {
1151                    if i > 0 {
1152                        self.write(", ");
1153                    }
1154                    self.gen_expr(val);
1155                }
1156                self.write(")");
1157            }
1158            MergeAction::InsertRow => {
1159                self.sep();
1160                self.write_keyword("INSERT ROW");
1161            }
1162            MergeAction::Delete => {
1163                self.sep();
1164                self.write_keyword("DELETE");
1165            }
1166        }
1167    }
1168
1169    // ── CREATE TABLE ────────────────────────────────────────────
1170
1171    fn gen_create_table(&mut self, ct: &CreateTableStatement) {
1172        self.write_keyword("CREATE ");
1173        if ct.temporary {
1174            self.write_keyword("TEMPORARY ");
1175        }
1176        self.write_keyword("TABLE ");
1177        if ct.if_not_exists {
1178            self.write_keyword("IF NOT EXISTS ");
1179        }
1180        self.gen_table_ref(&ct.table);
1181
1182        if let Some(as_select) = &ct.as_select {
1183            self.write(" ");
1184            self.write_keyword("AS ");
1185            self.gen_statement(as_select);
1186            return;
1187        }
1188
1189        self.write(" (");
1190
1191        if self.pretty {
1192            self.indent_up();
1193            for (i, col) in ct.columns.iter().enumerate() {
1194                self.newline();
1195                self.gen_column_def(col);
1196                if i < ct.columns.len() - 1 || !ct.constraints.is_empty() {
1197                    self.write(",");
1198                }
1199            }
1200            for (i, constraint) in ct.constraints.iter().enumerate() {
1201                self.newline();
1202                self.gen_table_constraint(constraint);
1203                if i < ct.constraints.len() - 1 {
1204                    self.write(",");
1205                }
1206            }
1207            self.indent_down();
1208            self.newline();
1209        } else {
1210            for (i, col) in ct.columns.iter().enumerate() {
1211                if i > 0 {
1212                    self.write(", ");
1213                }
1214                self.gen_column_def(col);
1215            }
1216            for (i, constraint) in ct.constraints.iter().enumerate() {
1217                if i + ct.columns.len() > 0 {
1218                    self.write(", ");
1219                }
1220                self.gen_table_constraint(constraint);
1221            }
1222        }
1223
1224        self.write(")");
1225    }
1226
1227    fn gen_column_def(&mut self, col: &ColumnDef) {
1228        self.write(&col.name);
1229        self.write(" ");
1230        self.gen_data_type(&col.data_type);
1231
1232        if col.primary_key {
1233            self.write(" ");
1234            self.write_keyword("PRIMARY KEY");
1235        }
1236        if col.unique {
1237            self.write(" ");
1238            self.write_keyword("UNIQUE");
1239        }
1240        if col.auto_increment {
1241            self.write(" ");
1242            self.write_keyword("AUTOINCREMENT");
1243        }
1244
1245        match col.nullable {
1246            Some(false) => {
1247                self.write(" ");
1248                self.write_keyword("NOT NULL");
1249            }
1250            Some(true) => {
1251                self.write(" ");
1252                self.write_keyword("NULL");
1253            }
1254            None => {}
1255        }
1256
1257        if let Some(default) = &col.default {
1258            self.write(" ");
1259            self.write_keyword("DEFAULT ");
1260            self.gen_expr(default);
1261        }
1262
1263        if let Some(collation) = &col.collation {
1264            self.write(" ");
1265            self.write_keyword("COLLATE ");
1266            self.write(collation);
1267        }
1268
1269        if let Some(comment) = &col.comment {
1270            self.write(" ");
1271            self.write_keyword("COMMENT '");
1272            self.write(&comment.replace('\'', "''"));
1273            self.write("'");
1274        }
1275    }
1276
1277    fn gen_table_constraint(&mut self, constraint: &TableConstraint) {
1278        match constraint {
1279            TableConstraint::PrimaryKey { name, columns } => {
1280                if let Some(name) = name {
1281                    self.write_keyword("CONSTRAINT ");
1282                    self.write(name);
1283                    self.write(" ");
1284                }
1285                self.write_keyword("PRIMARY KEY (");
1286                self.write(&columns.join(", "));
1287                self.write(")");
1288            }
1289            TableConstraint::Unique { name, columns } => {
1290                if let Some(name) = name {
1291                    self.write_keyword("CONSTRAINT ");
1292                    self.write(name);
1293                    self.write(" ");
1294                }
1295                self.write_keyword("UNIQUE (");
1296                self.write(&columns.join(", "));
1297                self.write(")");
1298            }
1299            TableConstraint::ForeignKey {
1300                name,
1301                columns,
1302                ref_table,
1303                ref_columns,
1304                on_delete,
1305                on_update,
1306            } => {
1307                if let Some(name) = name {
1308                    self.write_keyword("CONSTRAINT ");
1309                    self.write(name);
1310                    self.write(" ");
1311                }
1312                self.write_keyword("FOREIGN KEY (");
1313                self.write(&columns.join(", "));
1314                self.write(") ");
1315                self.write_keyword("REFERENCES ");
1316                self.gen_table_ref(ref_table);
1317                self.write(" (");
1318                self.write(&ref_columns.join(", "));
1319                self.write(")");
1320                if let Some(action) = on_delete {
1321                    self.write(" ");
1322                    self.write_keyword("ON DELETE ");
1323                    self.gen_referential_action(action);
1324                }
1325                if let Some(action) = on_update {
1326                    self.write(" ");
1327                    self.write_keyword("ON UPDATE ");
1328                    self.gen_referential_action(action);
1329                }
1330            }
1331            TableConstraint::Check { name, expr } => {
1332                if let Some(name) = name {
1333                    self.write_keyword("CONSTRAINT ");
1334                    self.write(name);
1335                    self.write(" ");
1336                }
1337                self.write_keyword("CHECK (");
1338                self.gen_expr(expr);
1339                self.write(")");
1340            }
1341        }
1342    }
1343
1344    fn gen_referential_action(&mut self, action: &ReferentialAction) {
1345        match action {
1346            ReferentialAction::Cascade => self.write_keyword("CASCADE"),
1347            ReferentialAction::Restrict => self.write_keyword("RESTRICT"),
1348            ReferentialAction::NoAction => self.write_keyword("NO ACTION"),
1349            ReferentialAction::SetNull => self.write_keyword("SET NULL"),
1350            ReferentialAction::SetDefault => self.write_keyword("SET DEFAULT"),
1351        }
1352    }
1353
1354    // ── DROP TABLE ──────────────────────────────────────────────
1355
1356    fn gen_drop_table(&mut self, dt: &DropTableStatement) {
1357        self.write_keyword("DROP TABLE ");
1358        if dt.if_exists {
1359            self.write_keyword("IF EXISTS ");
1360        }
1361        self.gen_table_ref(&dt.table);
1362        if dt.cascade {
1363            self.write(" ");
1364            self.write_keyword("CASCADE");
1365        }
1366    }
1367
1368    // ── ALTER TABLE ─────────────────────────────────────────────
1369
1370    fn gen_alter_table(&mut self, alt: &AlterTableStatement) {
1371        self.write_keyword("ALTER TABLE ");
1372        self.gen_table_ref(&alt.table);
1373
1374        for (i, action) in alt.actions.iter().enumerate() {
1375            if i > 0 {
1376                self.write(",");
1377            }
1378            self.write(" ");
1379            match action {
1380                AlterTableAction::AddColumn(col) => {
1381                    self.write_keyword("ADD COLUMN ");
1382                    self.gen_column_def(col);
1383                }
1384                AlterTableAction::DropColumn { name, if_exists } => {
1385                    self.write_keyword("DROP COLUMN ");
1386                    if *if_exists {
1387                        self.write_keyword("IF EXISTS ");
1388                    }
1389                    self.write(name);
1390                }
1391                AlterTableAction::RenameColumn { old_name, new_name } => {
1392                    self.write_keyword("RENAME COLUMN ");
1393                    self.write(old_name);
1394                    self.write(" ");
1395                    self.write_keyword("TO ");
1396                    self.write(new_name);
1397                }
1398                AlterTableAction::AlterColumnType { name, data_type } => {
1399                    self.write_keyword("ALTER COLUMN ");
1400                    self.write(name);
1401                    self.write(" ");
1402                    self.write_keyword("TYPE ");
1403                    self.gen_data_type(data_type);
1404                }
1405                AlterTableAction::AddConstraint(constraint) => {
1406                    self.write_keyword("ADD ");
1407                    self.gen_table_constraint(constraint);
1408                }
1409                AlterTableAction::DropConstraint { name } => {
1410                    self.write_keyword("DROP CONSTRAINT ");
1411                    self.write(name);
1412                }
1413                AlterTableAction::RenameTable { new_name } => {
1414                    self.write_keyword("RENAME TO ");
1415                    self.write(new_name);
1416                }
1417            }
1418        }
1419    }
1420
1421    // ── CREATE / DROP VIEW ──────────────────────────────────────
1422
1423    fn gen_create_view(&mut self, cv: &CreateViewStatement) {
1424        self.write_keyword("CREATE ");
1425        if cv.or_replace {
1426            self.write_keyword("OR REPLACE ");
1427        }
1428        if cv.materialized {
1429            self.write_keyword("MATERIALIZED ");
1430        }
1431        self.write_keyword("VIEW ");
1432        if cv.if_not_exists {
1433            self.write_keyword("IF NOT EXISTS ");
1434        }
1435        self.gen_table_ref(&cv.name);
1436
1437        if !cv.columns.is_empty() {
1438            self.write(" (");
1439            self.write(&cv.columns.join(", "));
1440            self.write(")");
1441        }
1442
1443        self.write(" ");
1444        self.write_keyword("AS ");
1445        self.gen_statement(&cv.query);
1446    }
1447
1448    fn gen_drop_view(&mut self, dv: &DropViewStatement) {
1449        self.write_keyword("DROP ");
1450        if dv.materialized {
1451            self.write_keyword("MATERIALIZED ");
1452        }
1453        self.write_keyword("VIEW ");
1454        if dv.if_exists {
1455            self.write_keyword("IF EXISTS ");
1456        }
1457        self.gen_table_ref(&dv.name);
1458    }
1459
1460    // ── TRUNCATE ────────────────────────────────────────────────
1461
1462    fn gen_truncate(&mut self, t: &TruncateStatement) {
1463        self.write_keyword("TRUNCATE TABLE ");
1464        self.gen_table_ref(&t.table);
1465    }
1466
1467    // ── Transaction ─────────────────────────────────────────────
1468
1469    fn gen_transaction(&mut self, t: &TransactionStatement) {
1470        match t {
1471            TransactionStatement::Begin => self.write_keyword("BEGIN"),
1472            TransactionStatement::Commit => self.write_keyword("COMMIT"),
1473            TransactionStatement::Rollback => self.write_keyword("ROLLBACK"),
1474            TransactionStatement::Savepoint(name) => {
1475                self.write_keyword("SAVEPOINT ");
1476                self.write(name);
1477            }
1478            TransactionStatement::ReleaseSavepoint(name) => {
1479                self.write_keyword("RELEASE SAVEPOINT ");
1480                self.write(name);
1481            }
1482            TransactionStatement::RollbackTo(name) => {
1483                self.write_keyword("ROLLBACK TO SAVEPOINT ");
1484                self.write(name);
1485            }
1486        }
1487    }
1488
1489    // ── EXPLAIN ─────────────────────────────────────────────────
1490
1491    fn gen_explain(&mut self, e: &ExplainStatement) {
1492        self.write_keyword("EXPLAIN ");
1493        if e.analyze {
1494            self.write_keyword("ANALYZE ");
1495        }
1496        self.gen_statement(&e.statement);
1497    }
1498
1499    // ── USE ─────────────────────────────────────────────────────
1500
1501    fn gen_use(&mut self, u: &UseStatement) {
1502        self.write_keyword("USE ");
1503        self.write(&u.name);
1504    }
1505
1506    // ══════════════════════════════════════════════════════════════
1507    // Data types
1508    // ══════════════════════════════════════════════════════════════
1509
1510    fn gen_data_type(&mut self, dt: &DataType) {
1511        match dt {
1512            DataType::TinyInt => self.write("TINYINT"),
1513            DataType::SmallInt => self.write("SMALLINT"),
1514            DataType::Int => self.write("INT"),
1515            DataType::BigInt => self.write("BIGINT"),
1516            DataType::Float => self.write("FLOAT"),
1517            DataType::Double => self.write("DOUBLE"),
1518            DataType::Real => self.write("REAL"),
1519            DataType::Decimal { precision, scale } | DataType::Numeric { precision, scale } => {
1520                self.write(if matches!(dt, DataType::Numeric { .. }) {
1521                    "NUMERIC"
1522                } else {
1523                    "DECIMAL"
1524                });
1525                if let Some(p) = precision {
1526                    self.write(&format!("({p}"));
1527                    if let Some(s) = scale {
1528                        self.write(&format!(", {s}"));
1529                    }
1530                    self.write(")");
1531                }
1532            }
1533            DataType::Varchar(len) => {
1534                self.write("VARCHAR");
1535                if let Some(n) = len {
1536                    self.write(&format!("({n})"));
1537                }
1538            }
1539            DataType::Char(len) => {
1540                self.write("CHAR");
1541                if let Some(n) = len {
1542                    self.write(&format!("({n})"));
1543                }
1544            }
1545            DataType::Text => self.write("TEXT"),
1546            DataType::String => self.write("STRING"),
1547            DataType::Binary(len) => {
1548                self.write("BINARY");
1549                if let Some(n) = len {
1550                    self.write(&format!("({n})"));
1551                }
1552            }
1553            DataType::Varbinary(len) => {
1554                self.write("VARBINARY");
1555                match len {
1556                    Some(n) => self.write(&format!("({n})")),
1557                    None if matches!(self.dialect, Some(Dialect::Tsql) | Some(Dialect::Fabric)) => {
1558                        self.write("(MAX)");
1559                    }
1560                    None => {}
1561                }
1562            }
1563            DataType::NChar(len) => {
1564                // National fixed-length char is spelled NCHAR only for the
1565                // T-SQL family; elsewhere it degrades to CHAR.
1566                let is_tsql = matches!(self.dialect, Some(Dialect::Tsql) | Some(Dialect::Fabric));
1567                self.write(if is_tsql { "NCHAR" } else { "CHAR" });
1568                if let Some(n) = len {
1569                    self.write(&format!("({n})"));
1570                }
1571            }
1572            DataType::NVarchar(len) => {
1573                let is_tsql = matches!(self.dialect, Some(Dialect::Tsql) | Some(Dialect::Fabric));
1574                self.write(if is_tsql { "NVARCHAR" } else { "VARCHAR" });
1575                if let Some(n) = len {
1576                    self.write(&format!("({n})"));
1577                }
1578            }
1579            DataType::VarcharMax => {
1580                // Large-object string: VARCHAR(MAX) for the T-SQL family, TEXT
1581                // elsewhere. Preserves the full length instead of collapsing
1582                // to the MSSQL CAST default of 30.
1583                if matches!(self.dialect, Some(Dialect::Tsql) | Some(Dialect::Fabric)) {
1584                    self.write("VARCHAR(MAX)");
1585                } else {
1586                    self.write("TEXT");
1587                }
1588            }
1589            DataType::NvarcharMax => {
1590                if matches!(self.dialect, Some(Dialect::Tsql) | Some(Dialect::Fabric)) {
1591                    self.write("NVARCHAR(MAX)");
1592                } else {
1593                    self.write("TEXT");
1594                }
1595            }
1596            DataType::Varchar2(len) => {
1597                // Oracle's canonical variable-length string. Spelled VARCHAR2
1598                // only for Oracle; every other dialect uses portable VARCHAR.
1599                let is_oracle = matches!(self.dialect, Some(Dialect::Oracle));
1600                self.write(if is_oracle { "VARCHAR2" } else { "VARCHAR" });
1601                if let Some(n) = len {
1602                    self.write(&format!("({n})"));
1603                }
1604            }
1605            DataType::NVarchar2(len) => {
1606                // Oracle national variable-length string. Oracle → NVARCHAR2;
1607                // T-SQL family → NVARCHAR; everything else → VARCHAR.
1608                match self.dialect {
1609                    Some(Dialect::Oracle) => self.write("NVARCHAR2"),
1610                    Some(Dialect::Tsql) | Some(Dialect::Fabric) => self.write("NVARCHAR"),
1611                    _ => self.write("VARCHAR"),
1612                }
1613                if let Some(n) = len {
1614                    self.write(&format!("({n})"));
1615                }
1616            }
1617            DataType::Boolean => self.write("BOOLEAN"),
1618            DataType::Date => self.write("DATE"),
1619            DataType::Time { precision } => {
1620                self.write("TIME");
1621                if let Some(p) = precision {
1622                    self.write(&format!("({p})"));
1623                }
1624            }
1625            DataType::Timestamp { precision, with_tz } => {
1626                self.write("TIMESTAMP");
1627                if let Some(p) = precision {
1628                    self.write(&format!("({p})"));
1629                }
1630                if *with_tz {
1631                    self.write(" WITH TIME ZONE");
1632                }
1633            }
1634            DataType::Interval => self.write("INTERVAL"),
1635            DataType::DateTime => self.write("DATETIME"),
1636            DataType::Blob => self.write("BLOB"),
1637            DataType::Bytea => self.write("BYTEA"),
1638            DataType::Bytes => self.write("BYTES"),
1639            DataType::Json => self.write("JSON"),
1640            DataType::Jsonb => self.write("JSONB"),
1641            DataType::Uuid => self.write("UUID"),
1642            DataType::Array(inner) => {
1643                let is_postgres = matches!(
1644                    self.dialect,
1645                    Some(
1646                        Dialect::Postgres
1647                            | Dialect::Redshift
1648                            | Dialect::Materialize
1649                            | Dialect::RisingWave
1650                    )
1651                );
1652                if is_postgres {
1653                    // PostgreSQL: emit "typename[]"
1654                    if let Some(inner) = inner {
1655                        self.gen_data_type(inner);
1656                        self.write("[]");
1657                    } else {
1658                        self.write("ARRAY");
1659                    }
1660                } else {
1661                    self.write("ARRAY");
1662                    if let Some(inner) = inner {
1663                        self.write("<");
1664                        self.gen_data_type(inner);
1665                        self.write(">");
1666                    }
1667                }
1668            }
1669            DataType::Map { key, value } => {
1670                self.write("MAP<");
1671                self.gen_data_type(key);
1672                self.write(", ");
1673                self.gen_data_type(value);
1674                self.write(">");
1675            }
1676            DataType::Struct(fields) => {
1677                self.write("STRUCT<");
1678                for (i, (name, dt)) in fields.iter().enumerate() {
1679                    if i > 0 {
1680                        self.write(", ");
1681                    }
1682                    self.write(name);
1683                    self.write(" ");
1684                    self.gen_data_type(dt);
1685                }
1686                self.write(">");
1687            }
1688            DataType::Tuple(types) => {
1689                self.write("TUPLE(");
1690                for (i, dt) in types.iter().enumerate() {
1691                    if i > 0 {
1692                        self.write(", ");
1693                    }
1694                    self.gen_data_type(dt);
1695                }
1696                self.write(")");
1697            }
1698            DataType::Null => self.write("NULL"),
1699            DataType::Variant => self.write("VARIANT"),
1700            DataType::Object => self.write("OBJECT"),
1701            DataType::Xml => self.write("XML"),
1702            DataType::Inet => self.write("INET"),
1703            DataType::Cidr => self.write("CIDR"),
1704            DataType::Macaddr => self.write("MACADDR"),
1705            DataType::Bit(len) => {
1706                self.write("BIT");
1707                if let Some(n) = len {
1708                    self.write(&format!("({n})"));
1709                }
1710            }
1711            DataType::Money => self.write("MONEY"),
1712            DataType::Serial => self.write("SERIAL"),
1713            DataType::BigSerial => self.write("BIGSERIAL"),
1714            DataType::SmallSerial => self.write("SMALLSERIAL"),
1715            DataType::Regclass => self.write("REGCLASS"),
1716            DataType::Regtype => self.write("REGTYPE"),
1717            DataType::Hstore => self.write("HSTORE"),
1718            DataType::Geography => self.write("GEOGRAPHY"),
1719            DataType::Geometry => self.write("GEOMETRY"),
1720            DataType::Super => self.write("SUPER"),
1721            DataType::Unknown(name) => self.write(name),
1722        }
1723    }
1724
1725    // ══════════════════════════════════════════════════════════════
1726    // Expressions
1727    // ══════════════════════════════════════════════════════════════
1728
1729    fn binary_op_str(op: &BinaryOperator) -> &'static str {
1730        match op {
1731            BinaryOperator::Plus => " + ",
1732            BinaryOperator::Minus => " - ",
1733            BinaryOperator::Multiply => " * ",
1734            BinaryOperator::Divide => " / ",
1735            BinaryOperator::Modulo => " % ",
1736            BinaryOperator::Eq => " = ",
1737            BinaryOperator::Neq => " <> ",
1738            BinaryOperator::Lt => " < ",
1739            BinaryOperator::Gt => " > ",
1740            BinaryOperator::LtEq => " <= ",
1741            BinaryOperator::GtEq => " >= ",
1742            BinaryOperator::And => " AND ",
1743            BinaryOperator::Or => " OR ",
1744            BinaryOperator::Xor => " XOR ",
1745            BinaryOperator::Concat => " || ",
1746            BinaryOperator::BitwiseAnd => " & ",
1747            BinaryOperator::BitwiseOr => " | ",
1748            BinaryOperator::BitwiseXor => " ^ ",
1749            BinaryOperator::ShiftLeft => " << ",
1750            BinaryOperator::ShiftRight => " >> ",
1751            BinaryOperator::Arrow => " -> ",
1752            BinaryOperator::DoubleArrow => " ->> ",
1753            BinaryOperator::AtArrow => " @> ",
1754            BinaryOperator::ArrowAt => " <@ ",
1755        }
1756    }
1757
1758    fn gen_expr_list(&mut self, exprs: &[Expr]) {
1759        for (i, expr) in exprs.iter().enumerate() {
1760            if i > 0 {
1761                self.write(", ");
1762            }
1763            self.gen_expr(expr);
1764        }
1765    }
1766
1767    /// Emit `e` in a *condition* (search-condition) position.
1768    ///
1769    /// SQL Server has no native boolean type: a bare boolean expression (a
1770    /// `bit` column, function result, or boolean literal) is not a valid
1771    /// predicate and is rejected with error 4145 ("An expression of non-boolean
1772    /// type specified in a context where a condition is expected"). For the
1773    /// T-SQL family we wrap such a bare boolean as `<e> = 1`, recursing through
1774    /// the logical connectives (`AND`/`OR`/`XOR`/`NOT`/parentheses) so each
1775    /// nested operand is fixed, and lowering a bare boolean literal to `1 = 1`
1776    /// / `1 = 0`. Expressions that are already predicates (comparisons,
1777    /// `IS NULL`, `IN`, `LIKE`, `BETWEEN`, `EXISTS`, …) are emitted unchanged.
1778    ///
1779    /// Oracle has the same restriction for a *bare boolean literal* (`WHERE 1`
1780    /// and `WHERE TRUE` are not valid conditions), so it also lowers
1781    /// `TRUE`/`FALSE` to `1 = 1` / `1 = 0`, recursing through the connectives —
1782    /// but, unlike the T-SQL family, it does NOT wrap other bare scalars as
1783    /// `<e> = 1` (PSQ-2848). Every other dialect delegates straight to
1784    /// `gen_expr`, so their output is byte-for-byte identical.
1785    fn gen_condition(&mut self, e: &Expr) {
1786        let is_tsql = matches!(self.dialect, Some(d) if crate::dialects::is_tsql_family(d));
1787        let is_oracle = matches!(self.dialect, Some(Dialect::Oracle));
1788        if !is_tsql && !is_oracle {
1789            self.gen_expr(e);
1790            return;
1791        }
1792        match e {
1793            // Logical connectives: each operand is itself a condition position.
1794            Expr::BinaryOp { left, op, right }
1795                if matches!(
1796                    op,
1797                    BinaryOperator::And | BinaryOperator::Or | BinaryOperator::Xor
1798                ) =>
1799            {
1800                self.gen_condition(left);
1801                self.write(Self::binary_op_str(op));
1802                self.gen_condition(right);
1803            }
1804            Expr::UnaryOp {
1805                op: UnaryOperator::Not,
1806                expr,
1807            } => {
1808                self.write("NOT ");
1809                self.gen_condition(expr);
1810            }
1811            Expr::Nested(inner) => {
1812                self.write("(");
1813                self.gen_condition(inner);
1814                self.write(")");
1815            }
1816            // Already boolean-typed predicates: emit unchanged.
1817            Expr::BinaryOp {
1818                op:
1819                    BinaryOperator::Eq
1820                    | BinaryOperator::Neq
1821                    | BinaryOperator::Lt
1822                    | BinaryOperator::Gt
1823                    | BinaryOperator::LtEq
1824                    | BinaryOperator::GtEq,
1825                ..
1826            }
1827            | Expr::Between { .. }
1828            | Expr::IsNull { .. }
1829            | Expr::IsBool { .. }
1830            | Expr::InList { .. }
1831            | Expr::InSubquery { .. }
1832            | Expr::Like { .. }
1833            | Expr::ILike { .. }
1834            | Expr::SimilarTo { .. }
1835            | Expr::Exists { .. }
1836            | Expr::AnyOp { .. }
1837            | Expr::AllOp { .. } => self.gen_expr(e),
1838            // Bare boolean literal in a condition position → `1 = 1` / `1 = 0`.
1839            Expr::Boolean(b) => self.write(if *b { "1 = 1" } else { "1 = 0" }),
1840            // Any other bare scalar (column, function, cast, scalar subquery, …)
1841            // is a PostgreSQL boolean here. SQL Server wraps it into a predicate
1842            // (`<e> = 1`); Oracle leaves it unchanged, since the generator makes
1843            // no assumption about non-literal Oracle expressions.
1844            _ => {
1845                self.gen_expr(e);
1846                if is_tsql {
1847                    self.write(" = 1");
1848                }
1849            }
1850        }
1851    }
1852
1853    fn gen_expr(&mut self, expr: &Expr) {
1854        match expr {
1855            Expr::Column {
1856                table,
1857                name,
1858                quote_style,
1859                table_quote_style,
1860            } => {
1861                if let Some(t) = table {
1862                    self.write_quoted(t, *table_quote_style);
1863                    self.write(".");
1864                }
1865                self.write_quoted(name, *quote_style);
1866            }
1867            Expr::Number(n) => self.write(n),
1868            Expr::StringLiteral(s) => {
1869                // TSQL and Oracle require N'...' prefix for string literals containing
1870                // non-ASCII characters to prevent code-page corruption (CR-007).
1871                if matches!(self.dialect, Some(Dialect::Oracle) | Some(Dialect::Tsql))
1872                    && !s.is_ascii()
1873                {
1874                    self.write("N'");
1875                } else {
1876                    self.write("'");
1877                }
1878                self.write(&s.replace('\'', "''"));
1879                self.write("'");
1880            }
1881            Expr::NationalStringLiteral(s) => {
1882                if matches!(self.dialect, Some(Dialect::Oracle) | Some(Dialect::Tsql)) {
1883                    self.write("N'");
1884                } else {
1885                    self.write("'");
1886                }
1887                self.write(&s.replace('\'', "''"));
1888                self.write("'");
1889            }
1890            Expr::Boolean(b) => {
1891                // SQL Server/Fabric have no boolean type and Oracle (<= 21c) has
1892                // no boolean literal, so emit 1/0 for all three in operand and
1893                // projection positions (e.g. `col = TRUE` -> `col = 1`,
1894                // `SELECT TRUE` -> `SELECT 1`). Other dialects keep TRUE/FALSE.
1895                // Bare booleans in a *condition* position are handled by
1896                // `gen_condition`. (Oracle: PSQ-2848)
1897                if matches!(
1898                    self.dialect,
1899                    Some(Dialect::Tsql) | Some(Dialect::Fabric) | Some(Dialect::Oracle)
1900                ) {
1901                    self.write(if *b { "1" } else { "0" });
1902                } else {
1903                    self.write(if *b { "TRUE" } else { "FALSE" });
1904                }
1905            }
1906            Expr::Null => self.write("NULL"),
1907            Expr::Default => self.write_keyword("DEFAULT"),
1908            Expr::Wildcard | Expr::Star => self.write("*"),
1909
1910            Expr::Cube { exprs } => {
1911                self.write_keyword("CUBE");
1912                self.write("(");
1913                self.gen_expr_list(exprs);
1914                self.write(")");
1915            }
1916            Expr::Rollup { exprs } => {
1917                self.write_keyword("ROLLUP");
1918                self.write("(");
1919                self.gen_expr_list(exprs);
1920                self.write(")");
1921            }
1922            Expr::GroupingSets { sets } => {
1923                self.write_keyword("GROUPING SETS");
1924                self.write("(");
1925                self.gen_expr_list(sets);
1926                self.write(")");
1927            }
1928
1929            Expr::BinaryOp { left, op, right } => {
1930                self.gen_expr(left);
1931                self.write(Self::binary_op_str(op));
1932                self.gen_expr(right);
1933            }
1934            Expr::AnyOp { expr, op, right } => {
1935                self.gen_expr(expr);
1936                self.write(Self::binary_op_str(op));
1937                self.write_keyword("ANY");
1938                self.write("(");
1939                if let Expr::Subquery(query) = right.as_ref() {
1940                    self.gen_statement(query);
1941                } else {
1942                    self.gen_expr(right);
1943                }
1944                self.write(")");
1945            }
1946            Expr::AllOp { expr, op, right } => {
1947                self.gen_expr(expr);
1948                self.write(Self::binary_op_str(op));
1949                self.write_keyword("ALL");
1950                self.write("(");
1951                if let Expr::Subquery(query) = right.as_ref() {
1952                    self.gen_statement(query);
1953                } else {
1954                    self.gen_expr(right);
1955                }
1956                self.write(")");
1957            }
1958            Expr::UnaryOp { op, expr } => {
1959                let op_str = match op {
1960                    UnaryOperator::Not => "NOT ",
1961                    UnaryOperator::Minus => "-",
1962                    UnaryOperator::Plus => "+",
1963                    UnaryOperator::BitwiseNot => "~",
1964                };
1965                self.write(op_str);
1966                self.gen_expr(expr);
1967            }
1968            Expr::Function {
1969                name,
1970                args,
1971                distinct,
1972                filter,
1973                over,
1974                order_by,
1975                within_group,
1976            } => {
1977                self.write(name);
1978                self.write("(");
1979                if *distinct {
1980                    self.write_keyword("DISTINCT ");
1981                }
1982                self.gen_expr_list(args);
1983                // Aggregate ORDER BY embedded in the argument list (not WITHIN GROUP).
1984                if !*within_group && !order_by.is_empty() {
1985                    self.write(" ");
1986                    self.write_keyword("ORDER BY ");
1987                    self.gen_order_by_items_inline(order_by);
1988                }
1989                self.write(")");
1990
1991                // WITHIN GROUP (ORDER BY ...) — ordered-set aggregates.
1992                if *within_group && !order_by.is_empty() {
1993                    self.write(" ");
1994                    self.write_keyword("WITHIN GROUP (ORDER BY ");
1995                    self.gen_order_by_items_inline(order_by);
1996                    self.write(")");
1997                }
1998
1999                if let Some(filter_expr) = filter {
2000                    self.write(" ");
2001                    self.write_keyword("FILTER (WHERE ");
2002                    self.gen_expr(filter_expr);
2003                    self.write(")");
2004                }
2005                if let Some(spec) = over {
2006                    self.write(" ");
2007                    self.write_keyword("OVER ");
2008                    if let Some(wref) = &spec.window_ref {
2009                        if spec.partition_by.is_empty()
2010                            && spec.order_by.is_empty()
2011                            && spec.frame.is_none()
2012                        {
2013                            self.write(wref);
2014                        } else {
2015                            self.write("(");
2016                            self.gen_window_spec(spec);
2017                            self.write(")");
2018                        }
2019                    } else {
2020                        self.write("(");
2021                        self.gen_window_spec(spec);
2022                        self.write(")");
2023                    }
2024                }
2025            }
2026            Expr::Between {
2027                expr,
2028                low,
2029                high,
2030                negated,
2031            } => {
2032                self.gen_expr(expr);
2033                if *negated {
2034                    self.write(" ");
2035                    self.write_keyword("NOT");
2036                }
2037                self.write(" ");
2038                self.write_keyword("BETWEEN ");
2039                self.gen_expr(low);
2040                self.write(" ");
2041                self.write_keyword("AND ");
2042                self.gen_expr(high);
2043            }
2044            Expr::InList {
2045                expr,
2046                list,
2047                negated,
2048            } => {
2049                self.gen_expr(expr);
2050                if *negated {
2051                    self.write(" ");
2052                    self.write_keyword("NOT");
2053                }
2054                self.write(" ");
2055                self.write_keyword("IN (");
2056                self.gen_expr_list(list);
2057                self.write(")");
2058            }
2059            Expr::InSubquery {
2060                expr,
2061                subquery,
2062                negated,
2063            } => {
2064                self.gen_expr(expr);
2065                if *negated {
2066                    self.write(" ");
2067                    self.write_keyword("NOT");
2068                }
2069                self.write(" ");
2070                self.write_keyword("IN (");
2071                self.gen_statement(subquery);
2072                self.write(")");
2073            }
2074            Expr::IsNull { expr, negated } => {
2075                self.gen_expr(expr);
2076                if *negated {
2077                    self.write(" ");
2078                    self.write_keyword("IS NOT NULL");
2079                } else {
2080                    self.write(" ");
2081                    self.write_keyword("IS NULL");
2082                }
2083            }
2084            Expr::IsBool {
2085                expr,
2086                value,
2087                negated,
2088            } => {
2089                self.gen_expr(expr);
2090                self.write(" ");
2091                match (negated, value) {
2092                    (false, true) => self.write_keyword("IS TRUE"),
2093                    (false, false) => self.write_keyword("IS FALSE"),
2094                    (true, true) => self.write_keyword("IS NOT TRUE"),
2095                    (true, false) => self.write_keyword("IS NOT FALSE"),
2096                }
2097            }
2098            Expr::Like {
2099                expr,
2100                pattern,
2101                negated,
2102                escape,
2103            } => {
2104                self.gen_expr(expr);
2105                if *negated {
2106                    self.write(" ");
2107                    self.write_keyword("NOT");
2108                }
2109                self.write(" ");
2110                self.write_keyword("LIKE ");
2111                self.gen_expr(pattern);
2112                if let Some(esc) = escape {
2113                    self.write(" ");
2114                    self.write_keyword("ESCAPE ");
2115                    self.gen_expr(esc);
2116                }
2117            }
2118            Expr::ILike {
2119                expr,
2120                pattern,
2121                negated,
2122                escape,
2123            } => {
2124                self.gen_expr(expr);
2125                if *negated {
2126                    self.write(" ");
2127                    self.write_keyword("NOT");
2128                }
2129                self.write(" ");
2130                self.write_keyword("ILIKE ");
2131                self.gen_expr(pattern);
2132                if let Some(esc) = escape {
2133                    self.write(" ");
2134                    self.write_keyword("ESCAPE ");
2135                    self.gen_expr(esc);
2136                }
2137            }
2138            Expr::SimilarTo {
2139                expr,
2140                pattern,
2141                negated,
2142                escape,
2143            } => {
2144                self.gen_expr(expr);
2145                if *negated {
2146                    self.write(" ");
2147                    self.write_keyword("NOT");
2148                }
2149                self.write(" ");
2150                self.write_keyword("SIMILAR TO ");
2151                self.gen_expr(pattern);
2152                if let Some(esc) = escape {
2153                    self.write(" ");
2154                    self.write_keyword("ESCAPE ");
2155                    self.gen_expr(esc);
2156                }
2157            }
2158            Expr::Case {
2159                operand,
2160                when_clauses,
2161                else_clause,
2162            } => {
2163                self.write_keyword("CASE");
2164                if let Some(op) = operand {
2165                    self.write(" ");
2166                    self.gen_expr(op);
2167                }
2168                for (cond, result) in when_clauses {
2169                    self.write(" ");
2170                    self.write_keyword("WHEN ");
2171                    // A simple CASE (`CASE <operand> WHEN <value>`) compares each
2172                    // WHEN value against the operand, so it is NOT a condition
2173                    // position. Only a searched CASE (`CASE WHEN <cond>`) puts a
2174                    // boolean search condition here and needs T-SQL wrapping.
2175                    if operand.is_some() {
2176                        self.gen_expr(cond);
2177                    } else {
2178                        self.gen_condition(cond);
2179                    }
2180                    self.write(" ");
2181                    self.write_keyword("THEN ");
2182                    self.gen_expr(result);
2183                }
2184                if let Some(el) = else_clause {
2185                    self.write(" ");
2186                    self.write_keyword("ELSE ");
2187                    self.gen_expr(el);
2188                }
2189                self.write(" ");
2190                self.write_keyword("END");
2191            }
2192            Expr::Nested(inner) => {
2193                self.write("(");
2194                self.gen_expr(inner);
2195                self.write(")");
2196            }
2197            Expr::Subquery(query) => {
2198                self.write("(");
2199                self.gen_statement(query);
2200                self.write(")");
2201            }
2202            Expr::Exists { subquery, negated } => {
2203                if *negated {
2204                    self.write_keyword("NOT ");
2205                }
2206                self.write_keyword("EXISTS (");
2207                self.gen_statement(subquery);
2208                self.write(")");
2209            }
2210            Expr::Cast { expr, data_type } => {
2211                // ANSI typed string literals: emit DATE 'x' / TIMESTAMP 'x' / TIME 'x'
2212                if let Expr::StringLiteral(val) = expr.as_ref() {
2213                    let ansi_keyword = match data_type {
2214                        DataType::Date => Some("DATE"),
2215                        DataType::Timestamp { .. } => Some("TIMESTAMP"),
2216                        DataType::Time { .. } => Some("TIME"),
2217                        _ => None,
2218                    };
2219                    if let Some(kw) = ansi_keyword {
2220                        // Emit the ANSI typed string literal (DATE 'x' / TIME 'x' /
2221                        // TIMESTAMP 'x') only for dialects that support it. The MySQL
2222                        // family and the T-SQL family (SQL Server, Fabric) do NOT —
2223                        // they must use CAST(... AS DATE|TIME), produced by the
2224                        // fallthrough below. SQL Server rejects ANSI date/time
2225                        // literals with syntax errors 102/156.
2226                        let ansi_typed_literal_unsupported = matches!(
2227                            self.dialect,
2228                            Some(
2229                                Dialect::Mysql
2230                                    | Dialect::Doris
2231                                    | Dialect::SingleStore
2232                                    | Dialect::StarRocks
2233                                    | Dialect::Tsql
2234                                    | Dialect::Fabric
2235                            )
2236                        );
2237                        if !ansi_typed_literal_unsupported {
2238                            self.write_keyword(kw);
2239                            self.write(" '");
2240                            self.write(val);
2241                            self.write("'");
2242                            return;
2243                        }
2244                    }
2245                }
2246
2247                let is_postgres = matches!(
2248                    self.dialect,
2249                    Some(
2250                        Dialect::Postgres
2251                            | Dialect::Redshift
2252                            | Dialect::Materialize
2253                            | Dialect::RisingWave
2254                    )
2255                );
2256                if is_postgres {
2257                    self.gen_expr(expr);
2258                    self.write("::");
2259                    self.gen_data_type(data_type);
2260                } else {
2261                    self.write_keyword("CAST(");
2262                    self.gen_expr(expr);
2263                    self.write(" ");
2264                    self.write_keyword("AS ");
2265                    self.gen_data_type(data_type);
2266                    self.write(")");
2267                }
2268            }
2269            Expr::TryCast { expr, data_type } => {
2270                self.write_keyword("TRY_CAST(");
2271                self.gen_expr(expr);
2272                self.write(" ");
2273                self.write_keyword("AS ");
2274                self.gen_data_type(data_type);
2275                self.write(")");
2276            }
2277            Expr::Extract { field, expr } => {
2278                if matches!(self.dialect, Some(Dialect::Tsql) | Some(Dialect::Fabric)) {
2279                    match field {
2280                        DateTimeField::Epoch => {
2281                            // EXTRACT(EPOCH FROM x) → DATEDIFF(SECOND, '1970-01-01', x)
2282                            self.write_keyword("DATEDIFF(SECOND, '1970-01-01', ");
2283                            self.gen_expr(expr);
2284                            self.write(")");
2285                        }
2286                        DateTimeField::DayOfWeek => {
2287                            // PG DOW = 0(Sun)..6(Sat); T-SQL DATEPART(weekday, ..) = 1..7
2288                            // and is @@DATEFIRST-dependent. Preserve PG numbering in a
2289                            // @@DATEFIRST-independent way. Parentheses are required: T-SQL
2290                            // `%` binds tighter than `+`/`-`.
2291                            self.write_keyword("(DATEPART(WEEKDAY, ");
2292                            self.gen_expr(expr);
2293                            self.write_keyword(") + @@DATEFIRST - 1) % 7");
2294                        }
2295                        _ => {
2296                            self.write_keyword("DATEPART(");
2297                            match field {
2298                                // Postgres spellings that are not valid T-SQL dateparts.
2299                                DateTimeField::DayOfYear => self.write("DAYOFYEAR"),
2300                                // PG WEEK is ISO-8601; plain T-SQL `week` is not.
2301                                DateTimeField::Week => self.write("ISO_WEEK"),
2302                                // YEAR/QUARTER/MONTH/DAY/HOUR/MINUTE/SECOND/MILLI/MICRO/NANO
2303                                // already render to valid dateparts. TIMEZONE/TIMEZONE_HOUR/
2304                                // TIMEZONE_MINUTE have no equivalent and fall through to an
2305                                // invalid name on purpose (fail-safe hard error rather than
2306                                // silently wrong units).
2307                                _ => self.gen_datetime_field(field),
2308                            }
2309                            self.write(", ");
2310                            self.gen_expr(expr);
2311                            self.write(")");
2312                        }
2313                    }
2314                } else if matches!(self.dialect, Some(Dialect::Oracle)) {
2315                    // Oracle EXTRACT accepts only YEAR/MONTH/DAY/HOUR/MINUTE/SECOND/
2316                    // TIMEZONE_*. The Postgres-only fields (QUARTER/WEEK/DOY/DOW) must be
2317                    // rewritten or Oracle rejects the field name (ORA-00907). TO_CHAR
2318                    // returns VARCHAR2, so wrap in TO_NUMBER to preserve Postgres'
2319                    // numeric EXTRACT result type (parity for GROUP BY/ORDER BY/compare).
2320                    match field {
2321                        DateTimeField::Quarter => {
2322                            self.write_keyword("TO_NUMBER(TO_CHAR(");
2323                            self.gen_expr(expr);
2324                            self.write_keyword(", 'Q'))");
2325                        }
2326                        DateTimeField::Week => {
2327                            // PG EXTRACT(WEEK) is ISO-8601 → 'IW' (NOT 'WW', Jan-1-based).
2328                            self.write_keyword("TO_NUMBER(TO_CHAR(");
2329                            self.gen_expr(expr);
2330                            self.write_keyword(", 'IW'))");
2331                        }
2332                        DateTimeField::DayOfYear => {
2333                            self.write_keyword("TO_NUMBER(TO_CHAR(");
2334                            self.gen_expr(expr);
2335                            self.write_keyword(", 'DDD'))");
2336                        }
2337                        DateTimeField::DayOfWeek => {
2338                            // PG DOW = 0(Sun)..6(Sat). TO_CHAR(x,'D') is NLS_TERRITORY-
2339                            // dependent, so anchor on a known Sunday (1970-01-04) and take
2340                            // the day count mod 7 — NLS-independent, preserves PG numbering.
2341                            self.write_keyword("MOD(TRUNC(");
2342                            self.gen_expr(expr);
2343                            self.write_keyword(") - DATE '1970-01-04', 7)");
2344                        }
2345                        _ => {
2346                            // YEAR/MONTH/DAY/HOUR/MINUTE/SECOND/TIMEZONE_HOUR/TIMEZONE_MINUTE
2347                            // are valid Oracle EXTRACT fields — keep the native form. Fields
2348                            // with no Oracle equivalent (EPOCH, MILLI/MICRO/NANO, bare
2349                            // TIMEZONE) fall through to a native EXTRACT Oracle rejects — a
2350                            // fail-safe hard error rather than silently-wrong output.
2351                            self.write_keyword("EXTRACT(");
2352                            self.gen_datetime_field(field);
2353                            self.write(" ");
2354                            self.write_keyword("FROM ");
2355                            self.gen_expr(expr);
2356                            self.write(")");
2357                        }
2358                    }
2359                } else {
2360                    self.write_keyword("EXTRACT(");
2361                    self.gen_datetime_field(field);
2362                    self.write(" ");
2363                    self.write_keyword("FROM ");
2364                    self.gen_expr(expr);
2365                    self.write(")");
2366                }
2367            }
2368            Expr::Interval { value, unit } => {
2369                self.write_keyword("INTERVAL ");
2370                self.gen_expr(value);
2371                if let Some(unit) = unit {
2372                    self.write(" ");
2373                    self.gen_datetime_field(unit);
2374                }
2375            }
2376            Expr::ArrayLiteral(items) => {
2377                self.write_keyword("ARRAY[");
2378                self.gen_expr_list(items);
2379                self.write("]");
2380            }
2381            Expr::Tuple(items) => {
2382                self.write("(");
2383                self.gen_expr_list(items);
2384                self.write(")");
2385            }
2386            Expr::Coalesce(items) => {
2387                self.write_keyword("COALESCE(");
2388                self.gen_expr_list(items);
2389                self.write(")");
2390            }
2391            Expr::If {
2392                condition,
2393                true_val,
2394                false_val,
2395            } => {
2396                self.write_keyword("IF(");
2397                self.gen_expr(condition);
2398                self.write(", ");
2399                self.gen_expr(true_val);
2400                if let Some(fv) = false_val {
2401                    self.write(", ");
2402                    self.gen_expr(fv);
2403                }
2404                self.write(")");
2405            }
2406            Expr::NullIf { expr, r#else } => {
2407                self.write_keyword("NULLIF(");
2408                self.gen_expr(expr);
2409                self.write(", ");
2410                self.gen_expr(r#else);
2411                self.write(")");
2412            }
2413            Expr::Collate { expr, collation } => {
2414                self.gen_expr(expr);
2415                self.write(" ");
2416                self.write_keyword("COLLATE ");
2417                self.write(collation);
2418            }
2419            Expr::Parameter(p) => self.write(p),
2420            Expr::TypeExpr(dt) => self.gen_data_type(dt),
2421            Expr::QualifiedWildcard { table } => {
2422                self.write(table);
2423                self.write(".*");
2424            }
2425            Expr::Alias { expr, name } => {
2426                self.gen_expr(expr);
2427                self.write(" ");
2428                self.write_keyword("AS ");
2429                self.write(name);
2430            }
2431            Expr::ArrayIndex { expr, index } => {
2432                self.gen_expr(expr);
2433                self.write("[");
2434                self.gen_expr(index);
2435                self.write("]");
2436            }
2437            Expr::JsonAccess {
2438                expr,
2439                path,
2440                as_text,
2441            } => {
2442                self.gen_expr(expr);
2443                if *as_text {
2444                    self.write("->>");
2445                } else {
2446                    self.write("->");
2447                }
2448                self.gen_expr(path);
2449            }
2450            Expr::Lambda { params, body } => {
2451                if params.len() == 1 {
2452                    self.write(&params[0]);
2453                } else {
2454                    self.write("(");
2455                    self.write(&params.join(", "));
2456                    self.write(")");
2457                }
2458                self.write(" -> ");
2459                self.gen_expr(body);
2460            }
2461            Expr::TypedFunction { func, filter, over } => {
2462                self.gen_typed_function(func);
2463
2464                if let Some(filter_expr) = filter {
2465                    self.write(" ");
2466                    self.write_keyword("FILTER (WHERE ");
2467                    self.gen_expr(filter_expr);
2468                    self.write(")");
2469                }
2470                if let Some(spec) = over {
2471                    self.write(" ");
2472                    self.write_keyword("OVER ");
2473                    if let Some(wref) = &spec.window_ref {
2474                        if spec.partition_by.is_empty()
2475                            && spec.order_by.is_empty()
2476                            && spec.frame.is_none()
2477                        {
2478                            self.write(wref);
2479                        } else {
2480                            self.write("(");
2481                            self.gen_window_spec(spec);
2482                            self.write(")");
2483                        }
2484                    } else {
2485                        self.write("(");
2486                        self.gen_window_spec(spec);
2487                        self.write(")");
2488                    }
2489                }
2490            }
2491            Expr::Commented { expr, comments } => {
2492                for comment in comments {
2493                    let normalized = self.normalize_comment(comment);
2494                    self.write(&normalized);
2495                    self.write(" ");
2496                }
2497                self.gen_expr(expr);
2498            }
2499        }
2500    }
2501
2502    fn gen_window_spec(&mut self, spec: &WindowSpec) {
2503        if let Some(wref) = &spec.window_ref {
2504            self.write(wref);
2505            if !spec.partition_by.is_empty() || !spec.order_by.is_empty() || spec.frame.is_some() {
2506                self.write(" ");
2507            }
2508        }
2509        if !spec.partition_by.is_empty() {
2510            self.write_keyword("PARTITION BY ");
2511            self.gen_expr_list(&spec.partition_by);
2512        }
2513        if !spec.order_by.is_empty() {
2514            if !spec.partition_by.is_empty() {
2515                self.write(" ");
2516            }
2517            self.write_keyword("ORDER BY ");
2518            for (i, item) in spec.order_by.iter().enumerate() {
2519                if i > 0 {
2520                    self.write(", ");
2521                }
2522                self.gen_expr(&item.expr);
2523                if !item.ascending {
2524                    self.write(" ");
2525                    self.write_keyword("DESC");
2526                }
2527                if let Some(nulls_first) = item.nulls_first {
2528                    if nulls_first {
2529                        self.write(" ");
2530                        self.write_keyword("NULLS FIRST");
2531                    } else {
2532                        self.write(" ");
2533                        self.write_keyword("NULLS LAST");
2534                    }
2535                }
2536            }
2537        }
2538        if let Some(frame) = &spec.frame {
2539            self.write(" ");
2540            self.gen_window_frame(frame);
2541        }
2542    }
2543
2544    fn gen_window_frame(&mut self, frame: &WindowFrame) {
2545        match frame.kind {
2546            WindowFrameKind::Rows => self.write_keyword("ROWS "),
2547            WindowFrameKind::Range => self.write_keyword("RANGE "),
2548            WindowFrameKind::Groups => self.write_keyword("GROUPS "),
2549        }
2550        if let Some(end) = &frame.end {
2551            self.write_keyword("BETWEEN ");
2552            self.gen_window_frame_bound(&frame.start);
2553            self.write(" ");
2554            self.write_keyword("AND ");
2555            self.gen_window_frame_bound(end);
2556        } else {
2557            self.gen_window_frame_bound(&frame.start);
2558        }
2559    }
2560
2561    fn gen_window_frame_bound(&mut self, bound: &WindowFrameBound) {
2562        match bound {
2563            WindowFrameBound::CurrentRow => self.write_keyword("CURRENT ROW"),
2564            WindowFrameBound::Preceding(None) => self.write_keyword("UNBOUNDED PRECEDING"),
2565            WindowFrameBound::Preceding(Some(n)) => {
2566                self.gen_expr(n);
2567                self.write(" ");
2568                self.write_keyword("PRECEDING");
2569            }
2570            WindowFrameBound::Following(None) => self.write_keyword("UNBOUNDED FOLLOWING"),
2571            WindowFrameBound::Following(Some(n)) => {
2572                self.gen_expr(n);
2573                self.write(" ");
2574                self.write_keyword("FOLLOWING");
2575            }
2576        }
2577    }
2578
2579    fn gen_datetime_field(&mut self, field: &DateTimeField) {
2580        let name = match field {
2581            DateTimeField::Year => "YEAR",
2582            DateTimeField::Quarter => "QUARTER",
2583            DateTimeField::Month => "MONTH",
2584            DateTimeField::Week => "WEEK",
2585            DateTimeField::Day => "DAY",
2586            DateTimeField::DayOfWeek => "DOW",
2587            DateTimeField::DayOfYear => "DOY",
2588            DateTimeField::Hour => "HOUR",
2589            DateTimeField::Minute => "MINUTE",
2590            DateTimeField::Second => "SECOND",
2591            DateTimeField::Millisecond => "MILLISECOND",
2592            DateTimeField::Microsecond => "MICROSECOND",
2593            DateTimeField::Nanosecond => "NANOSECOND",
2594            DateTimeField::Epoch => "EPOCH",
2595            DateTimeField::Timezone => "TIMEZONE",
2596            DateTimeField::TimezoneHour => "TIMEZONE_HOUR",
2597            DateTimeField::TimezoneMinute => "TIMEZONE_MINUTE",
2598        };
2599        self.write(name);
2600    }
2601
2602    /// Generate SQL for a typed function expression.
2603    fn gen_typed_function(&mut self, func: &TypedFunction) {
2604        let dialect = self.dialect;
2605        let is_tsql = matches!(dialect, Some(Dialect::Tsql) | Some(Dialect::Fabric));
2606        let is_mysql = matches!(
2607            dialect,
2608            Some(Dialect::Mysql)
2609                | Some(Dialect::SingleStore)
2610                | Some(Dialect::Doris)
2611                | Some(Dialect::StarRocks)
2612        );
2613        let is_bigquery = matches!(dialect, Some(Dialect::BigQuery));
2614        let is_snowflake = matches!(dialect, Some(Dialect::Snowflake));
2615        let is_oracle = matches!(dialect, Some(Dialect::Oracle));
2616        let is_hive_family = matches!(
2617            dialect,
2618            Some(Dialect::Hive) | Some(Dialect::Spark) | Some(Dialect::Databricks)
2619        );
2620
2621        match func {
2622            // ── Date/Time ──────────────────────────────────────────────
2623            TypedFunction::DateAdd {
2624                expr,
2625                interval,
2626                unit,
2627            } => {
2628                if is_tsql || is_snowflake {
2629                    self.write_keyword("DATEADD(");
2630                    if let Some(u) = unit {
2631                        self.gen_datetime_field(u);
2632                    } else {
2633                        self.write_keyword("DAY");
2634                    }
2635                    self.write(", ");
2636                    self.gen_expr(interval);
2637                    self.write(", ");
2638                    self.gen_expr(expr);
2639                    self.write(")");
2640                } else if is_bigquery {
2641                    self.write_keyword("DATE_ADD(");
2642                    self.gen_expr(expr);
2643                    self.write(", ");
2644                    self.write_keyword("INTERVAL ");
2645                    self.gen_expr(interval);
2646                    self.write(" ");
2647                    if let Some(u) = unit {
2648                        self.gen_datetime_field(u);
2649                    } else {
2650                        self.write_keyword("DAY");
2651                    }
2652                    self.write(")");
2653                } else {
2654                    self.write_keyword("DATE_ADD(");
2655                    self.gen_expr(expr);
2656                    self.write(", ");
2657                    self.gen_expr(interval);
2658                    if let Some(u) = unit {
2659                        self.write(", ");
2660                        self.gen_datetime_field(u);
2661                    }
2662                    self.write(")");
2663                }
2664            }
2665            TypedFunction::DateDiff { start, end, unit } => {
2666                if is_tsql || is_snowflake {
2667                    self.write_keyword("DATEDIFF(");
2668                    if let Some(u) = unit {
2669                        self.gen_datetime_field(u);
2670                    } else {
2671                        self.write_keyword("DAY");
2672                    }
2673                    self.write(", ");
2674                    self.gen_expr(start);
2675                    self.write(", ");
2676                    self.gen_expr(end);
2677                    self.write(")");
2678                } else if is_bigquery {
2679                    self.write_keyword("DATE_DIFF(");
2680                    self.gen_expr(end);
2681                    self.write(", ");
2682                    self.gen_expr(start);
2683                    self.write(", ");
2684                    if let Some(u) = unit {
2685                        self.gen_datetime_field(u);
2686                    } else {
2687                        self.write_keyword("DAY");
2688                    }
2689                    self.write(")");
2690                } else {
2691                    self.write_keyword("DATEDIFF(");
2692                    self.gen_expr(start);
2693                    self.write(", ");
2694                    self.gen_expr(end);
2695                    if let Some(u) = unit {
2696                        self.write(", ");
2697                        self.gen_datetime_field(u);
2698                    }
2699                    self.write(")");
2700                }
2701            }
2702            TypedFunction::DateTrunc { unit, expr } => {
2703                if is_tsql {
2704                    self.write_keyword("DATETRUNC(");
2705                    self.gen_datetime_field(unit);
2706                    self.write(", ");
2707                    self.gen_expr(expr);
2708                    self.write(")");
2709                } else if is_oracle {
2710                    self.write_keyword("TRUNC(");
2711                    self.gen_expr(expr);
2712                    self.write(", '");
2713                    self.gen_datetime_field(unit);
2714                    self.write("')");
2715                } else {
2716                    self.write_keyword("DATE_TRUNC(");
2717                    self.write("'");
2718                    self.gen_datetime_field(unit);
2719                    self.write("'");
2720                    self.write(", ");
2721                    self.gen_expr(expr);
2722                    self.write(")");
2723                }
2724            }
2725            TypedFunction::DateSub {
2726                expr,
2727                interval,
2728                unit,
2729            } => {
2730                if is_tsql || is_snowflake {
2731                    self.write_keyword("DATEADD(");
2732                    if let Some(u) = unit {
2733                        self.gen_datetime_field(u);
2734                    } else {
2735                        self.write_keyword("DAY");
2736                    }
2737                    self.write(", -(");
2738                    self.gen_expr(interval);
2739                    self.write("), ");
2740                    self.gen_expr(expr);
2741                    self.write(")");
2742                } else if is_bigquery {
2743                    self.write_keyword("DATE_SUB(");
2744                    self.gen_expr(expr);
2745                    self.write(", ");
2746                    self.write_keyword("INTERVAL ");
2747                    self.gen_expr(interval);
2748                    self.write(" ");
2749                    if let Some(u) = unit {
2750                        self.gen_datetime_field(u);
2751                    } else {
2752                        self.write_keyword("DAY");
2753                    }
2754                    self.write(")");
2755                } else {
2756                    self.write_keyword("DATE_SUB(");
2757                    self.gen_expr(expr);
2758                    self.write(", ");
2759                    self.gen_expr(interval);
2760                    if let Some(u) = unit {
2761                        self.write(", ");
2762                        self.gen_datetime_field(u);
2763                    }
2764                    self.write(")");
2765                }
2766            }
2767            TypedFunction::CurrentDate => {
2768                if is_tsql {
2769                    self.write_keyword("CAST(GETDATE() AS DATE)");
2770                } else if is_mysql || is_hive_family {
2771                    self.write_keyword("CURRENT_DATE()");
2772                } else {
2773                    self.write_keyword("CURRENT_DATE");
2774                }
2775            }
2776            TypedFunction::CurrentTime => {
2777                if is_tsql {
2778                    self.write_keyword("CAST(GETDATE() AS TIME)");
2779                } else if is_mysql || is_hive_family {
2780                    self.write_keyword("CURRENT_TIME()");
2781                } else {
2782                    self.write_keyword("CURRENT_TIME");
2783                }
2784            }
2785            TypedFunction::CurrentTimestamp => {
2786                if is_tsql {
2787                    self.write_keyword("GETDATE()");
2788                } else if is_mysql
2789                    || matches!(
2790                        dialect,
2791                        Some(Dialect::Postgres)
2792                            | Some(Dialect::DuckDb)
2793                            | Some(Dialect::Sqlite)
2794                            | Some(Dialect::Redshift)
2795                    )
2796                {
2797                    self.write_keyword("NOW()");
2798                } else {
2799                    self.write_keyword("CURRENT_TIMESTAMP()");
2800                }
2801            }
2802            TypedFunction::StrToTime { expr, format } => {
2803                if is_mysql {
2804                    self.write_keyword("STR_TO_DATE(");
2805                } else if is_bigquery {
2806                    self.write_keyword("PARSE_TIMESTAMP(");
2807                } else {
2808                    self.write_keyword("TO_TIMESTAMP(");
2809                }
2810                self.gen_expr(expr);
2811                self.write(", ");
2812                self.gen_expr(format);
2813                self.write(")");
2814            }
2815            TypedFunction::TimeToStr { expr, format } => {
2816                if is_mysql || is_hive_family {
2817                    self.write_keyword("DATE_FORMAT(");
2818                } else if is_bigquery {
2819                    self.write_keyword("FORMAT_TIMESTAMP(");
2820                } else if is_tsql {
2821                    self.write_keyword("FORMAT(");
2822                } else {
2823                    self.write_keyword("TO_CHAR(");
2824                }
2825                self.gen_expr(expr);
2826                self.write(", ");
2827                self.gen_expr(format);
2828                self.write(")");
2829            }
2830            TypedFunction::TsOrDsToDate { expr } => {
2831                if is_mysql {
2832                    self.write_keyword("DATE(");
2833                    self.gen_expr(expr);
2834                    self.write(")");
2835                } else {
2836                    self.write_keyword("CAST(");
2837                    self.gen_expr(expr);
2838                    self.write(" ");
2839                    self.write_keyword("AS DATE)");
2840                }
2841            }
2842            TypedFunction::Year { expr } => {
2843                if is_tsql {
2844                    self.write_keyword("YEAR(");
2845                    self.gen_expr(expr);
2846                    self.write(")");
2847                } else {
2848                    self.write_keyword("EXTRACT(YEAR FROM ");
2849                    self.gen_expr(expr);
2850                    self.write(")");
2851                }
2852            }
2853            TypedFunction::Month { expr } => {
2854                if is_tsql {
2855                    self.write_keyword("MONTH(");
2856                    self.gen_expr(expr);
2857                    self.write(")");
2858                } else {
2859                    self.write_keyword("EXTRACT(MONTH FROM ");
2860                    self.gen_expr(expr);
2861                    self.write(")");
2862                }
2863            }
2864            TypedFunction::Day { expr } => {
2865                if is_tsql {
2866                    self.write_keyword("DAY(");
2867                    self.gen_expr(expr);
2868                    self.write(")");
2869                } else {
2870                    self.write_keyword("EXTRACT(DAY FROM ");
2871                    self.gen_expr(expr);
2872                    self.write(")");
2873                }
2874            }
2875
2876            // ── String ─────────────────────────────────────────────────
2877            TypedFunction::Trim {
2878                expr,
2879                trim_type,
2880                trim_chars,
2881            } => {
2882                // Dialects without the ANSI `TRIM([LEADING|TRAILING] chars FROM
2883                // expr)` grammar express direction via LTRIM/RTRIM and take the
2884                // trim characters as an optional second argument.
2885                let comma_form = matches!(
2886                    dialect,
2887                    Some(Dialect::Snowflake)
2888                        | Some(Dialect::BigQuery)
2889                        | Some(Dialect::Sqlite)
2890                        | Some(Dialect::DuckDb)
2891                );
2892                // Hive proper supports only single-argument TRIM/LTRIM/RTRIM: no
2893                // FROM grammar and no custom trim characters.
2894                let hive_only = matches!(dialect, Some(Dialect::Hive));
2895                if comma_form || hive_only {
2896                    let fname = match trim_type {
2897                        TrimType::Leading => "LTRIM(",
2898                        TrimType::Trailing => "RTRIM(",
2899                        TrimType::Both => "TRIM(",
2900                    };
2901                    self.write_keyword(fname);
2902                    self.gen_expr(expr);
2903                    // Hive has no way to express a custom trim character set, so
2904                    // it is dropped (best effort); comma-form dialects keep it.
2905                    if !hive_only && let Some(chars) = trim_chars {
2906                        self.write(", ");
2907                        self.gen_expr(chars);
2908                    }
2909                    self.write(")");
2910                } else if is_tsql && trim_chars.is_none() {
2911                    // T-SQL (SQL Server / Fabric) requires a character set when a
2912                    // direction keyword is present: `TRIM(LEADING FROM x)` is
2913                    // rejected (Msg 156, "Incorrect syntax near 'FROM'"). With no
2914                    // trim characters, express the leading/trailing space-trim via
2915                    // the native LTRIM / RTRIM functions; BOTH with no chars is a
2916                    // bare TRIM(x). (CR-025)
2917                    let fname = match trim_type {
2918                        TrimType::Leading => "LTRIM(",
2919                        TrimType::Trailing => "RTRIM(",
2920                        TrimType::Both => "TRIM(",
2921                    };
2922                    self.write_keyword(fname);
2923                    self.gen_expr(expr);
2924                    self.write(")");
2925                } else {
2926                    // ANSI / FROM form (PostgreSQL, Oracle, MySQL, T-SQL 2022 when
2927                    // trim characters are supplied, Spark, Trino, ClickHouse,
2928                    // Redshift, …). FROM is required whenever a direction keyword
2929                    // is present OR a character set is given; a bare `TRIM(expr)`
2930                    // is emitted otherwise.
2931                    self.write_keyword("TRIM(");
2932                    match trim_type {
2933                        TrimType::Leading => self.write_keyword("LEADING "),
2934                        TrimType::Trailing => self.write_keyword("TRAILING "),
2935                        TrimType::Both => {} // BOTH is the default
2936                    }
2937                    let needs_from = trim_chars.is_some() || !matches!(trim_type, TrimType::Both);
2938                    if let Some(chars) = trim_chars {
2939                        self.gen_expr(chars);
2940                        self.write(" ");
2941                    }
2942                    if needs_from {
2943                        self.write_keyword("FROM ");
2944                    }
2945                    self.gen_expr(expr);
2946                    self.write(")");
2947                }
2948            }
2949            TypedFunction::Substring {
2950                expr,
2951                start,
2952                length,
2953            } => {
2954                let name = if is_oracle
2955                    || is_hive_family
2956                    || is_mysql
2957                    || matches!(
2958                        dialect,
2959                        Some(Dialect::Sqlite)
2960                            | Some(Dialect::Doris)
2961                            | Some(Dialect::SingleStore)
2962                            | Some(Dialect::StarRocks)
2963                    ) {
2964                    "SUBSTR"
2965                } else {
2966                    "SUBSTRING"
2967                };
2968                self.write_keyword(name);
2969                self.write("(");
2970                self.gen_expr(expr);
2971                self.write(", ");
2972                self.gen_expr(start);
2973                if let Some(l) = length {
2974                    self.write(", ");
2975                    self.gen_expr(l);
2976                }
2977                self.write(")");
2978            }
2979            TypedFunction::Upper { expr } => {
2980                self.write_keyword("UPPER(");
2981                self.gen_expr(expr);
2982                self.write(")");
2983            }
2984            TypedFunction::Lower { expr } => {
2985                self.write_keyword("LOWER(");
2986                self.gen_expr(expr);
2987                self.write(")");
2988            }
2989            TypedFunction::RegexpLike {
2990                expr,
2991                pattern,
2992                flags,
2993            } => {
2994                self.write_keyword("REGEXP_LIKE(");
2995                self.gen_expr(expr);
2996                self.write(", ");
2997                self.gen_expr(pattern);
2998                if let Some(f) = flags {
2999                    self.write(", ");
3000                    self.gen_expr(f);
3001                }
3002                self.write(")");
3003            }
3004            TypedFunction::RegexpExtract {
3005                expr,
3006                pattern,
3007                group_index,
3008            } => {
3009                if is_bigquery || is_hive_family {
3010                    self.write_keyword("REGEXP_EXTRACT(");
3011                } else {
3012                    self.write_keyword("REGEXP_SUBSTR(");
3013                }
3014                self.gen_expr(expr);
3015                self.write(", ");
3016                self.gen_expr(pattern);
3017                if let Some(g) = group_index {
3018                    self.write(", ");
3019                    self.gen_expr(g);
3020                }
3021                self.write(")");
3022            }
3023            TypedFunction::RegexpReplace {
3024                expr,
3025                pattern,
3026                replacement,
3027                flags,
3028            } => {
3029                self.write_keyword("REGEXP_REPLACE(");
3030                self.gen_expr(expr);
3031                self.write(", ");
3032                self.gen_expr(pattern);
3033                self.write(", ");
3034                self.gen_expr(replacement);
3035                if let Some(f) = flags {
3036                    self.write(", ");
3037                    self.gen_expr(f);
3038                }
3039                self.write(")");
3040            }
3041            TypedFunction::ConcatWs { separator, exprs } => {
3042                self.write_keyword("CONCAT_WS(");
3043                self.gen_expr(separator);
3044                for e in exprs {
3045                    self.write(", ");
3046                    self.gen_expr(e);
3047                }
3048                self.write(")");
3049            }
3050            TypedFunction::Split { expr, delimiter } => {
3051                if is_tsql {
3052                    self.write_keyword("STRING_SPLIT(");
3053                } else {
3054                    self.write_keyword("SPLIT(");
3055                }
3056                self.gen_expr(expr);
3057                self.write(", ");
3058                self.gen_expr(delimiter);
3059                self.write(")");
3060            }
3061            TypedFunction::Initcap { expr } => {
3062                self.write_keyword("INITCAP(");
3063                self.gen_expr(expr);
3064                self.write(")");
3065            }
3066            TypedFunction::Length { expr } => {
3067                let name = if is_tsql || is_bigquery || is_snowflake {
3068                    "LEN"
3069                } else {
3070                    "LENGTH"
3071                };
3072                self.write_keyword(name);
3073                self.write("(");
3074                self.gen_expr(expr);
3075                self.write(")");
3076            }
3077            TypedFunction::Replace { expr, from, to } => {
3078                self.write_keyword("REPLACE(");
3079                self.gen_expr(expr);
3080                self.write(", ");
3081                self.gen_expr(from);
3082                self.write(", ");
3083                self.gen_expr(to);
3084                self.write(")");
3085            }
3086            TypedFunction::Reverse { expr } => {
3087                self.write_keyword("REVERSE(");
3088                self.gen_expr(expr);
3089                self.write(")");
3090            }
3091            TypedFunction::Left { expr, n } => {
3092                self.write_keyword("LEFT(");
3093                self.gen_expr(expr);
3094                self.write(", ");
3095                self.gen_expr(n);
3096                self.write(")");
3097            }
3098            TypedFunction::Right { expr, n } => {
3099                self.write_keyword("RIGHT(");
3100                self.gen_expr(expr);
3101                self.write(", ");
3102                self.gen_expr(n);
3103                self.write(")");
3104            }
3105            TypedFunction::Lpad { expr, length, pad } => {
3106                self.write_keyword("LPAD(");
3107                self.gen_expr(expr);
3108                self.write(", ");
3109                self.gen_expr(length);
3110                if let Some(p) = pad {
3111                    self.write(", ");
3112                    self.gen_expr(p);
3113                }
3114                self.write(")");
3115            }
3116            TypedFunction::Rpad { expr, length, pad } => {
3117                self.write_keyword("RPAD(");
3118                self.gen_expr(expr);
3119                self.write(", ");
3120                self.gen_expr(length);
3121                if let Some(p) = pad {
3122                    self.write(", ");
3123                    self.gen_expr(p);
3124                }
3125                self.write(")");
3126            }
3127
3128            // ── Aggregate ──────────────────────────────────────────────
3129            TypedFunction::Count { expr, distinct } => {
3130                self.write_keyword("COUNT(");
3131                if *distinct {
3132                    self.write_keyword("DISTINCT ");
3133                }
3134                self.gen_expr(expr);
3135                self.write(")");
3136            }
3137            TypedFunction::Sum { expr, distinct } => {
3138                self.write_keyword("SUM(");
3139                if *distinct {
3140                    self.write_keyword("DISTINCT ");
3141                }
3142                self.gen_expr(expr);
3143                self.write(")");
3144            }
3145            TypedFunction::Avg { expr, distinct } => {
3146                self.write_keyword("AVG(");
3147                if *distinct {
3148                    self.write_keyword("DISTINCT ");
3149                }
3150                self.gen_expr(expr);
3151                self.write(")");
3152            }
3153            TypedFunction::Min { expr } => {
3154                self.write_keyword("MIN(");
3155                self.gen_expr(expr);
3156                self.write(")");
3157            }
3158            TypedFunction::Max { expr } => {
3159                self.write_keyword("MAX(");
3160                self.gen_expr(expr);
3161                self.write(")");
3162            }
3163            TypedFunction::ArrayAgg { expr, distinct } => {
3164                let name = if matches!(dialect, Some(Dialect::DuckDb)) {
3165                    "LIST"
3166                } else if is_hive_family {
3167                    "COLLECT_LIST"
3168                } else {
3169                    "ARRAY_AGG"
3170                };
3171                self.write_keyword(name);
3172                self.write("(");
3173                if *distinct {
3174                    self.write_keyword("DISTINCT ");
3175                }
3176                self.gen_expr(expr);
3177                self.write(")");
3178            }
3179            TypedFunction::ApproxDistinct { expr } => {
3180                let name = if is_hive_family
3181                    || matches!(
3182                        dialect,
3183                        Some(Dialect::Presto) | Some(Dialect::Trino) | Some(Dialect::Athena)
3184                    ) {
3185                    "APPROX_DISTINCT"
3186                } else {
3187                    "APPROX_COUNT_DISTINCT"
3188                };
3189                self.write_keyword(name);
3190                self.write("(");
3191                self.gen_expr(expr);
3192                self.write(")");
3193            }
3194            TypedFunction::Variance { expr } => {
3195                let name = if is_tsql || is_oracle {
3196                    "VAR"
3197                } else {
3198                    "VARIANCE"
3199                };
3200                self.write_keyword(name);
3201                self.write("(");
3202                self.gen_expr(expr);
3203                self.write(")");
3204            }
3205            TypedFunction::VariancePop { expr } => {
3206                // Population variance: PG VAR_POP -> T-SQL VARP.
3207                let name = if is_tsql { "VARP" } else { "VAR_POP" };
3208                self.write_keyword(name);
3209                self.write("(");
3210                self.gen_expr(expr);
3211                self.write(")");
3212            }
3213            TypedFunction::Stddev { expr } => {
3214                // Sample std-dev: PG STDDEV/STDDEV_SAMP -> T-SQL STDEV.
3215                // (Oracle natively supports STDDEV, so scope to T-SQL only.)
3216                let name = if is_tsql { "STDEV" } else { "STDDEV" };
3217                self.write_keyword(name);
3218                self.write("(");
3219                self.gen_expr(expr);
3220                self.write(")");
3221            }
3222            TypedFunction::StddevPop { expr } => {
3223                // Population std-dev: PG STDDEV_POP -> T-SQL STDEVP.
3224                // (Oracle natively supports STDDEV_POP, so scope to T-SQL only.)
3225                let name = if is_tsql { "STDEVP" } else { "STDDEV_POP" };
3226                self.write_keyword(name);
3227                self.write("(");
3228                self.gen_expr(expr);
3229                self.write(")");
3230            }
3231            TypedFunction::GroupConcat {
3232                exprs,
3233                separator,
3234                order_by,
3235                distinct,
3236            } => {
3237                self.gen_group_concat(exprs, separator.as_deref(), order_by, *distinct);
3238            }
3239
3240            // ── Array ──────────────────────────────────────────────────
3241            TypedFunction::ArrayConcat { arrays } => {
3242                let name = if matches!(
3243                    dialect,
3244                    Some(Dialect::Postgres) | Some(Dialect::Redshift) | Some(Dialect::DuckDb)
3245                ) {
3246                    "ARRAY_CAT"
3247                } else {
3248                    "ARRAY_CONCAT"
3249                };
3250                self.write_keyword(name);
3251                self.write("(");
3252                self.gen_expr_list(arrays);
3253                self.write(")");
3254            }
3255            TypedFunction::ArrayContains { array, element } => {
3256                self.write_keyword("ARRAY_CONTAINS(");
3257                self.gen_expr(array);
3258                self.write(", ");
3259                self.gen_expr(element);
3260                self.write(")");
3261            }
3262            TypedFunction::ArraySize { expr } => {
3263                let name = if matches!(dialect, Some(Dialect::Postgres) | Some(Dialect::Redshift)) {
3264                    "ARRAY_LENGTH"
3265                } else if is_hive_family {
3266                    "SIZE"
3267                } else {
3268                    "ARRAY_SIZE"
3269                };
3270                self.write_keyword(name);
3271                self.write("(");
3272                self.gen_expr(expr);
3273                self.write(")");
3274            }
3275            TypedFunction::Explode { expr } => {
3276                self.write_keyword("EXPLODE(");
3277                self.gen_expr(expr);
3278                self.write(")");
3279            }
3280            TypedFunction::GenerateSeries { start, stop, step } => {
3281                self.write_keyword("GENERATE_SERIES(");
3282                self.gen_expr(start);
3283                self.write(", ");
3284                self.gen_expr(stop);
3285                if let Some(s) = step {
3286                    self.write(", ");
3287                    self.gen_expr(s);
3288                }
3289                self.write(")");
3290            }
3291            TypedFunction::Flatten { expr } => {
3292                self.write_keyword("FLATTEN(");
3293                self.gen_expr(expr);
3294                self.write(")");
3295            }
3296
3297            // ── JSON ───────────────────────────────────────────────────
3298            TypedFunction::JSONExtract { expr, path } => {
3299                if is_tsql {
3300                    self.write_keyword("JSON_VALUE(");
3301                } else {
3302                    self.write_keyword("JSON_EXTRACT(");
3303                }
3304                self.gen_expr(expr);
3305                self.write(", ");
3306                self.gen_expr(path);
3307                self.write(")");
3308            }
3309            TypedFunction::JSONExtractScalar { expr, path } => {
3310                if is_bigquery {
3311                    self.write_keyword("JSON_EXTRACT_SCALAR(");
3312                } else if is_tsql {
3313                    self.write_keyword("JSON_VALUE(");
3314                } else {
3315                    self.write_keyword("JSON_EXTRACT_SCALAR(");
3316                }
3317                self.gen_expr(expr);
3318                self.write(", ");
3319                self.gen_expr(path);
3320                self.write(")");
3321            }
3322            TypedFunction::ParseJSON { expr } => {
3323                if is_snowflake {
3324                    self.write_keyword("PARSE_JSON(");
3325                } else if is_bigquery {
3326                    self.write_keyword("JSON_PARSE(");
3327                } else {
3328                    self.write_keyword("PARSE_JSON(");
3329                }
3330                self.gen_expr(expr);
3331                self.write(")");
3332            }
3333            TypedFunction::JSONFormat { expr } => {
3334                if is_bigquery {
3335                    self.write_keyword("TO_JSON_STRING(");
3336                } else {
3337                    self.write_keyword("JSON_FORMAT(");
3338                }
3339                self.gen_expr(expr);
3340                self.write(")");
3341            }
3342
3343            // ── Window ─────────────────────────────────────────────────
3344            TypedFunction::RowNumber => self.write_keyword("ROW_NUMBER()"),
3345            TypedFunction::Rank => self.write_keyword("RANK()"),
3346            TypedFunction::DenseRank => self.write_keyword("DENSE_RANK()"),
3347            TypedFunction::NTile { n } => {
3348                self.write_keyword("NTILE(");
3349                self.gen_expr(n);
3350                self.write(")");
3351            }
3352            TypedFunction::Lead {
3353                expr,
3354                offset,
3355                default,
3356            } => {
3357                self.write_keyword("LEAD(");
3358                self.gen_expr(expr);
3359                if let Some(o) = offset {
3360                    self.write(", ");
3361                    self.gen_expr(o);
3362                }
3363                if let Some(d) = default {
3364                    self.write(", ");
3365                    self.gen_expr(d);
3366                }
3367                self.write(")");
3368            }
3369            TypedFunction::Lag {
3370                expr,
3371                offset,
3372                default,
3373            } => {
3374                self.write_keyword("LAG(");
3375                self.gen_expr(expr);
3376                if let Some(o) = offset {
3377                    self.write(", ");
3378                    self.gen_expr(o);
3379                }
3380                if let Some(d) = default {
3381                    self.write(", ");
3382                    self.gen_expr(d);
3383                }
3384                self.write(")");
3385            }
3386            TypedFunction::FirstValue { expr } => {
3387                self.write_keyword("FIRST_VALUE(");
3388                self.gen_expr(expr);
3389                self.write(")");
3390            }
3391            TypedFunction::LastValue { expr } => {
3392                self.write_keyword("LAST_VALUE(");
3393                self.gen_expr(expr);
3394                self.write(")");
3395            }
3396
3397            // ── Math ───────────────────────────────────────────────────
3398            TypedFunction::Abs { expr } => {
3399                self.write_keyword("ABS(");
3400                self.gen_expr(expr);
3401                self.write(")");
3402            }
3403            TypedFunction::Ceil { expr } => {
3404                let name = if is_tsql { "CEILING" } else { "CEIL" };
3405                self.write_keyword(name);
3406                self.write("(");
3407                self.gen_expr(expr);
3408                self.write(")");
3409            }
3410            TypedFunction::Floor { expr } => {
3411                self.write_keyword("FLOOR(");
3412                self.gen_expr(expr);
3413                self.write(")");
3414            }
3415            TypedFunction::Round { expr, decimals } => {
3416                self.write_keyword("ROUND(");
3417                self.gen_expr(expr);
3418                if let Some(d) = decimals {
3419                    self.write(", ");
3420                    self.gen_expr(d);
3421                }
3422                self.write(")");
3423            }
3424            TypedFunction::Log { expr, base } => {
3425                if let Some(b) = base {
3426                    self.write_keyword("LOG(");
3427                    if matches!(dialect, Some(Dialect::Postgres) | Some(Dialect::DuckDb)) {
3428                        // Postgres: LOG(base, expr)
3429                        self.gen_expr(b);
3430                        self.write(", ");
3431                        self.gen_expr(expr);
3432                    } else {
3433                        // Most: LOG(expr, base)
3434                        self.gen_expr(expr);
3435                        self.write(", ");
3436                        self.gen_expr(b);
3437                    }
3438                    self.write(")");
3439                } else {
3440                    // LOG(expr) — ln in Postgres, log10 in most others
3441                    self.write_keyword("LOG(");
3442                    self.gen_expr(expr);
3443                    self.write(")");
3444                }
3445            }
3446            TypedFunction::Ln { expr } => {
3447                self.write_keyword("LN(");
3448                self.gen_expr(expr);
3449                self.write(")");
3450            }
3451            TypedFunction::Pow { base, exponent } => {
3452                let name = if is_tsql || is_oracle { "POWER" } else { "POW" };
3453                self.write_keyword(name);
3454                self.write("(");
3455                self.gen_expr(base);
3456                self.write(", ");
3457                self.gen_expr(exponent);
3458                self.write(")");
3459            }
3460            TypedFunction::Sqrt { expr } => {
3461                self.write_keyword("SQRT(");
3462                self.gen_expr(expr);
3463                self.write(")");
3464            }
3465            TypedFunction::Greatest { exprs } => {
3466                self.write_keyword("GREATEST(");
3467                self.gen_expr_list(exprs);
3468                self.write(")");
3469            }
3470            TypedFunction::Least { exprs } => {
3471                self.write_keyword("LEAST(");
3472                self.gen_expr_list(exprs);
3473                self.write(")");
3474            }
3475            TypedFunction::Mod { left, right } => {
3476                self.write_keyword("MOD(");
3477                self.gen_expr(left);
3478                self.write(", ");
3479                self.gen_expr(right);
3480                self.write(")");
3481            }
3482
3483            // ── Conversion ─────────────────────────────────────────────
3484            TypedFunction::Hex { expr } => {
3485                let name = if matches!(
3486                    dialect,
3487                    Some(Dialect::Presto) | Some(Dialect::Trino) | Some(Dialect::Athena)
3488                ) {
3489                    "TO_HEX"
3490                } else {
3491                    "HEX"
3492                };
3493                self.write_keyword(name);
3494                self.write("(");
3495                self.gen_expr(expr);
3496                self.write(")");
3497            }
3498            TypedFunction::Unhex { expr } => {
3499                let name = if matches!(
3500                    dialect,
3501                    Some(Dialect::Presto) | Some(Dialect::Trino) | Some(Dialect::Athena)
3502                ) {
3503                    "FROM_HEX"
3504                } else {
3505                    "UNHEX"
3506                };
3507                self.write_keyword(name);
3508                self.write("(");
3509                self.gen_expr(expr);
3510                self.write(")");
3511            }
3512            TypedFunction::Md5 { expr } => {
3513                self.write_keyword("MD5(");
3514                self.gen_expr(expr);
3515                self.write(")");
3516            }
3517            TypedFunction::Sha { expr } => {
3518                let name = if is_mysql { "SHA1" } else { "SHA" };
3519                self.write_keyword(name);
3520                self.write("(");
3521                self.gen_expr(expr);
3522                self.write(")");
3523            }
3524            TypedFunction::Sha2 { expr, bit_length } => {
3525                self.write_keyword("SHA2(");
3526                self.gen_expr(expr);
3527                self.write(", ");
3528                self.gen_expr(bit_length);
3529                self.write(")");
3530            }
3531        }
3532    }
3533
3534    /// Emit a `GROUP_CONCAT` / `STRING_AGG` / `LISTAGG` expression
3535    /// appropriately for the active dialect.
3536    fn gen_group_concat(
3537        &mut self,
3538        exprs: &[Expr],
3539        separator: Option<&Expr>,
3540        order_by: &[OrderByItem],
3541        distinct: bool,
3542    ) {
3543        let dialect = self.dialect;
3544        let is_sqlite = matches!(dialect, Some(Dialect::Sqlite));
3545        let is_string_agg = matches!(
3546            dialect,
3547            Some(Dialect::Postgres)
3548                | Some(Dialect::Redshift)
3549                | Some(Dialect::BigQuery)
3550                | Some(Dialect::Tsql)
3551                | Some(Dialect::Fabric)
3552                | Some(Dialect::DuckDb)
3553        );
3554        let is_listagg = matches!(dialect, Some(Dialect::Oracle) | Some(Dialect::Snowflake));
3555
3556        if is_string_agg {
3557            // STRING_AGG(expr, sep [ORDER BY ...])
3558            self.write_keyword("STRING_AGG(");
3559            if distinct {
3560                self.write_keyword("DISTINCT ");
3561            }
3562            self.gen_group_concat_exprs(exprs);
3563            self.write(", ");
3564            match separator {
3565                Some(s) => self.gen_expr(s),
3566                None => self.write("','"),
3567            }
3568            if !order_by.is_empty() {
3569                self.write(" ");
3570                self.gen_group_concat_order_by(order_by);
3571            }
3572            self.write(")");
3573        } else if is_listagg {
3574            // LISTAGG(expr, sep) WITHIN GROUP (ORDER BY ...)
3575            self.write_keyword("LISTAGG(");
3576            if distinct {
3577                self.write_keyword("DISTINCT ");
3578            }
3579            self.gen_group_concat_exprs(exprs);
3580            self.write(", ");
3581            match separator {
3582                Some(s) => self.gen_expr(s),
3583                None => self.write("','"),
3584            }
3585            self.write(")");
3586            if !order_by.is_empty() {
3587                self.write(" ");
3588                self.write_keyword("WITHIN GROUP");
3589                self.write(" (");
3590                self.gen_group_concat_order_by(order_by);
3591                self.write(")");
3592            }
3593        } else if is_sqlite {
3594            // SQLite: GROUP_CONCAT(expr[, sep]). No DISTINCT/ORDER BY support;
3595            // they are dropped on output since the target dialect lacks them.
3596            self.write_keyword("GROUP_CONCAT(");
3597            self.gen_group_concat_exprs(exprs);
3598            if let Some(s) = separator {
3599                self.write(", ");
3600                self.gen_expr(s);
3601            }
3602            self.write(")");
3603        } else {
3604            // MySQL family (and default): full GROUP_CONCAT grammar.
3605            self.write_keyword("GROUP_CONCAT(");
3606            if distinct {
3607                self.write_keyword("DISTINCT ");
3608            }
3609            self.gen_group_concat_exprs(exprs);
3610            if !order_by.is_empty() {
3611                self.write(" ");
3612                self.gen_group_concat_order_by(order_by);
3613            }
3614            if let Some(s) = separator {
3615                self.write(" ");
3616                self.write_keyword("SEPARATOR");
3617                self.write(" ");
3618                self.gen_expr(s);
3619            }
3620            self.write(")");
3621        }
3622    }
3623
3624    fn gen_group_concat_exprs(&mut self, exprs: &[Expr]) {
3625        for (i, e) in exprs.iter().enumerate() {
3626            if i > 0 {
3627                self.write(", ");
3628            }
3629            self.gen_expr(e);
3630        }
3631    }
3632
3633    fn gen_group_concat_order_by(&mut self, order_by: &[OrderByItem]) {
3634        self.write_keyword("ORDER BY");
3635        self.write(" ");
3636        for (i, item) in order_by.iter().enumerate() {
3637            if i > 0 {
3638                self.write(", ");
3639            }
3640            self.gen_expr(&item.expr);
3641            if !item.ascending {
3642                self.write(" ");
3643                self.write_keyword("DESC");
3644            }
3645            if let Some(nulls_first) = item.nulls_first {
3646                self.write(" ");
3647                self.write_keyword(if nulls_first {
3648                    "NULLS FIRST"
3649                } else {
3650                    "NULLS LAST"
3651                });
3652            }
3653        }
3654    }
3655}
3656
3657impl Default for Generator {
3658    fn default() -> Self {
3659        Self::new()
3660    }
3661}
3662
3663#[cfg(test)]
3664mod tests {
3665    use super::*;
3666    use crate::parser::Parser;
3667
3668    fn roundtrip(sql: &str) -> String {
3669        let stmt = Parser::new(sql).unwrap().parse_statement().unwrap();
3670        let mut g = Generator::new();
3671        g.generate(&stmt)
3672    }
3673
3674    #[test]
3675    fn test_select_roundtrip() {
3676        assert_eq!(roundtrip("SELECT a, b FROM t"), "SELECT a, b FROM t");
3677    }
3678
3679    #[test]
3680    fn test_select_where() {
3681        assert_eq!(
3682            roundtrip("SELECT x FROM t WHERE x > 10"),
3683            "SELECT x FROM t WHERE x > 10"
3684        );
3685    }
3686
3687    #[test]
3688    fn test_select_wildcard() {
3689        assert_eq!(roundtrip("SELECT * FROM users"), "SELECT * FROM users");
3690    }
3691
3692    #[test]
3693    fn test_insert_values() {
3694        assert_eq!(
3695            roundtrip("INSERT INTO t (a, b) VALUES (1, 'hello')"),
3696            "INSERT INTO t (a, b) VALUES (1, 'hello')"
3697        );
3698    }
3699
3700    #[test]
3701    fn test_delete() {
3702        assert_eq!(
3703            roundtrip("DELETE FROM users WHERE id = 1"),
3704            "DELETE FROM users WHERE id = 1"
3705        );
3706    }
3707
3708    #[test]
3709    fn test_join() {
3710        assert_eq!(
3711            roundtrip("SELECT a.id, b.name FROM a INNER JOIN b ON a.id = b.a_id"),
3712            "SELECT a.id, b.name FROM a INNER JOIN b ON a.id = b.a_id"
3713        );
3714    }
3715
3716    #[test]
3717    fn test_create_table() {
3718        assert_eq!(
3719            roundtrip("CREATE TABLE users (id INT NOT NULL, name VARCHAR(255), email TEXT)"),
3720            "CREATE TABLE users (id INT NOT NULL, name VARCHAR(255), email TEXT)"
3721        );
3722    }
3723
3724    #[test]
3725    fn test_cte_roundtrip() {
3726        let sql = "WITH cte AS (SELECT 1 AS x) SELECT x FROM cte";
3727        assert_eq!(
3728            roundtrip(sql),
3729            "WITH cte AS (SELECT 1 AS x) SELECT x FROM cte"
3730        );
3731    }
3732
3733    #[test]
3734    fn test_union_roundtrip() {
3735        let sql = "SELECT 1 UNION ALL SELECT 2";
3736        assert_eq!(roundtrip(sql), "SELECT 1 UNION ALL SELECT 2");
3737    }
3738
3739    #[test]
3740    fn test_cast_roundtrip() {
3741        assert_eq!(
3742            roundtrip("SELECT CAST(x AS INT) FROM t"),
3743            "SELECT CAST(x AS INT) FROM t"
3744        );
3745    }
3746
3747    #[test]
3748    fn test_exists_roundtrip() {
3749        assert_eq!(
3750            roundtrip("SELECT * FROM t WHERE EXISTS (SELECT 1 FROM t2)"),
3751            "SELECT * FROM t WHERE EXISTS (SELECT 1 FROM t2)"
3752        );
3753    }
3754
3755    #[test]
3756    fn test_extract_roundtrip() {
3757        assert_eq!(
3758            roundtrip("SELECT EXTRACT(YEAR FROM created_at) FROM t"),
3759            "SELECT EXTRACT(YEAR FROM created_at) FROM t"
3760        );
3761    }
3762
3763    #[test]
3764    fn test_window_function_roundtrip() {
3765        assert_eq!(
3766            roundtrip("SELECT ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) FROM emp"),
3767            "SELECT ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) FROM emp"
3768        );
3769    }
3770
3771    #[test]
3772    fn test_subquery_from_roundtrip() {
3773        assert_eq!(
3774            roundtrip("SELECT * FROM (SELECT 1 AS x) AS sub"),
3775            "SELECT * FROM (SELECT 1 AS x) AS sub"
3776        );
3777    }
3778
3779    #[test]
3780    fn test_in_subquery_roundtrip() {
3781        assert_eq!(
3782            roundtrip("SELECT * FROM t WHERE id IN (SELECT id FROM t2)"),
3783            "SELECT * FROM t WHERE id IN (SELECT id FROM t2)"
3784        );
3785    }
3786
3787    // ═══════════════════════════════════════════════════════════════
3788    // Pretty-print tests
3789    // ═══════════════════════════════════════════════════════════════
3790
3791    fn pretty_print(sql: &str) -> String {
3792        let stmt = Parser::new(sql).unwrap().parse_statement().unwrap();
3793        let mut g = Generator::pretty();
3794        g.generate(&stmt)
3795    }
3796
3797    #[test]
3798    fn test_pretty_simple_select() {
3799        assert_eq!(
3800            pretty_print("SELECT a, b, c FROM t"),
3801            "SELECT\n  a,\n  b,\n  c\nFROM\n  t"
3802        );
3803    }
3804
3805    #[test]
3806    fn test_pretty_select_where() {
3807        assert_eq!(
3808            pretty_print("SELECT a FROM t WHERE a > 1"),
3809            "SELECT\n  a\nFROM\n  t\nWHERE\n  a > 1"
3810        );
3811    }
3812
3813    #[test]
3814    fn test_pretty_select_group_by_having() {
3815        assert_eq!(
3816            pretty_print("SELECT a, COUNT(*) FROM t GROUP BY a HAVING COUNT(*) > 1"),
3817            "SELECT\n  a,\n  COUNT(*)\nFROM\n  t\nGROUP BY\n  a\nHAVING\n  COUNT(*) > 1"
3818        );
3819    }
3820
3821    #[test]
3822    fn test_pretty_select_order_by_limit() {
3823        assert_eq!(
3824            pretty_print("SELECT a FROM t ORDER BY a DESC LIMIT 10"),
3825            "SELECT\n  a\nFROM\n  t\nORDER BY\n  a DESC\nLIMIT 10"
3826        );
3827    }
3828
3829    #[test]
3830    fn test_pretty_join() {
3831        assert_eq!(
3832            pretty_print("SELECT a.id, b.name FROM a INNER JOIN b ON a.id = b.a_id"),
3833            "SELECT\n  a.id,\n  b.name\nFROM\n  a\nINNER JOIN\n  b\n  ON a.id = b.a_id"
3834        );
3835    }
3836
3837    #[test]
3838    fn test_pretty_cte() {
3839        assert_eq!(
3840            pretty_print("WITH cte AS (SELECT 1 AS x) SELECT x FROM cte"),
3841            "WITH cte AS (\n  SELECT\n    1 AS x\n)\nSELECT\n  x\nFROM\n  cte"
3842        );
3843    }
3844
3845    #[test]
3846    fn test_pretty_union() {
3847        assert_eq!(
3848            pretty_print("SELECT 1 UNION ALL SELECT 2"),
3849            "SELECT\n  1\nUNION ALL\nSELECT\n  2"
3850        );
3851    }
3852
3853    #[test]
3854    fn test_pretty_insert() {
3855        assert_eq!(
3856            pretty_print("INSERT INTO t (a, b) VALUES (1, 'hello'), (2, 'world')"),
3857            "INSERT INTO t (a, b)\nVALUES\n  (1, 'hello'),\n  (2, 'world')"
3858        );
3859    }
3860
3861    #[test]
3862    fn test_pretty_update() {
3863        assert_eq!(
3864            pretty_print("UPDATE t SET a = 1, b = 2 WHERE c = 3"),
3865            "UPDATE t\nSET\n  a = 1,\n  b = 2\nWHERE\n  c = 3"
3866        );
3867    }
3868
3869    #[test]
3870    fn test_pretty_delete() {
3871        assert_eq!(
3872            pretty_print("DELETE FROM t WHERE id = 1"),
3873            "DELETE FROM t\nWHERE\n  id = 1"
3874        );
3875    }
3876
3877    #[test]
3878    fn test_pretty_create_table() {
3879        assert_eq!(
3880            pretty_print("CREATE TABLE t (id INT NOT NULL, name VARCHAR(255), email TEXT)"),
3881            "CREATE TABLE t (\n  id INT NOT NULL,\n  name VARCHAR(255),\n  email TEXT\n)"
3882        );
3883    }
3884
3885    #[test]
3886    fn test_pretty_complex_query() {
3887        let sql = "SELECT a, SUM(b) FROM t1 INNER JOIN t2 ON t1.id = t2.id WHERE t1.x > 1 GROUP BY a HAVING SUM(b) > 10 ORDER BY a LIMIT 100";
3888        let expected = "SELECT\n  a,\n  SUM(b)\nFROM\n  t1\nINNER JOIN\n  t2\n  ON t1.id = t2.id\nWHERE\n  t1.x > 1\nGROUP BY\n  a\nHAVING\n  SUM(b) > 10\nORDER BY\n  a\nLIMIT 100";
3889        assert_eq!(pretty_print(sql), expected);
3890    }
3891
3892    #[test]
3893    fn test_pretty_select_distinct() {
3894        assert_eq!(
3895            pretty_print("SELECT DISTINCT a, b FROM t"),
3896            "SELECT DISTINCT\n  a,\n  b\nFROM\n  t"
3897        );
3898    }
3899}