Skip to main content

polyglot_sql/
generator.rs

1//! SQL Generator -- converts an AST back into SQL strings.
2//!
3//! The central type is [`Generator`], which walks an [`Expression`] tree and
4//! emits a SQL string. Generation is controlled by a [`GeneratorConfig`] that
5//! specifies the target dialect, formatting preferences, identifier quoting
6//! style, function name casing, and many other dialect-specific flags.
7//!
8//! For one-off generation the static helpers [`Generator::sql`] and
9//! [`Generator::pretty_sql`] are the simplest entry points. For repeated
10//! generation, construct a `Generator` once with [`Generator::with_config`]
11//! and call [`Generator::generate`] for each expression.
12
13use crate::error::Result;
14use crate::expressions::*;
15use crate::DialectType;
16
17/// SQL code generator that converts an AST (`Expression`) back into a SQL string.
18///
19/// The generator walks the expression tree and emits dialect-specific SQL text.
20/// It supports pretty-printing with configurable indentation, identifier quoting,
21/// keyword casing, function name normalization, and 30+ SQL dialect variants.
22///
23/// # Usage
24///
25/// ```rust,ignore
26/// use polyglot_sql::generator::Generator;
27/// use polyglot_sql::parser::Parser;
28///
29/// let ast = Parser::parse_sql("SELECT 1")?;
30/// // Quick one-shot generation (default config):
31/// let sql = Generator::sql(&ast[0])?;
32///
33/// // Pretty-printed output:
34/// let pretty = Generator::pretty_sql(&ast[0])?;
35///
36/// // Custom config (e.g. for a specific dialect):
37/// let config = GeneratorConfig { pretty: true, ..GeneratorConfig::default() };
38/// let mut gen = Generator::with_config(config);
39/// let sql = gen.generate(&ast[0])?;
40/// ```
41pub struct Generator {
42    config: GeneratorConfig,
43    output: String,
44    indent_level: usize,
45    /// Athena dialect: true when generating Hive-style DDL (uses backticks)
46    /// false when generating Trino-style DML/CREATE VIEW (uses double quotes)
47    athena_hive_context: bool,
48    /// SQLite: column names that should have PRIMARY KEY inlined (from single-column table constraints)
49    sqlite_inline_pk_columns: std::collections::HashSet<String>,
50    /// MERGE: table name/alias qualifiers to strip from UPDATE SET left side (for PostgreSQL)
51    merge_strip_qualifiers: Vec<String>,
52    /// ClickHouse: depth counter for Nullable wrapping context in CAST types.
53    /// 0 = not in cast context, 1 = top-level cast type, 2+ = inside container type.
54    /// Positive values indicate the type should be wrapped in Nullable (for non-container types).
55    /// Negative values indicate map key context (should NOT be wrapped).
56    clickhouse_nullable_depth: i32,
57}
58
59/// Controls how SQL function names are cased in generated output.
60///
61/// - `Upper` (default) -- `COUNT`, `SUM`, `COALESCE`
62/// - `Lower` -- `count`, `sum`, `coalesce`
63/// - `None` -- preserve the original casing from the parsed input
64#[derive(Debug, Clone, Copy, PartialEq, Default)]
65pub enum NormalizeFunctions {
66    /// Emit function names in UPPER CASE (default).
67    #[default]
68    Upper,
69    /// Emit function names in lower case.
70    Lower,
71    /// Preserve the original casing from the parsed input.
72    None,
73}
74
75/// Strategy for generating row-limiting clauses across SQL dialects.
76#[derive(Debug, Clone, Copy, PartialEq, Default)]
77pub enum LimitFetchStyle {
78    /// `LIMIT n` -- MySQL, PostgreSQL, DuckDB, and most modern dialects.
79    #[default]
80    Limit,
81    /// `TOP n` -- TSQL (SQL Server).
82    Top,
83    /// `FETCH FIRST n ROWS ONLY` -- ISO/ANSI SQL standard, Oracle, DB2.
84    FetchFirst,
85}
86
87/// Strategy for rendering negated IN predicates.
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
89pub enum NotInStyle {
90    /// Emit `NOT x IN (...)` in generic mode (current compatibility behavior).
91    #[default]
92    Prefix,
93    /// Emit `x NOT IN (...)` in generic mode (canonical SQL style).
94    Infix,
95}
96
97/// Identifier quote style (start/end characters)
98#[derive(Debug, Clone, Copy, PartialEq)]
99pub struct IdentifierQuoteStyle {
100    /// Start character for quoting identifiers (e.g., '"', '`', '[')
101    pub start: char,
102    /// End character for quoting identifiers (e.g., '"', '`', ']')
103    pub end: char,
104}
105
106impl Default for IdentifierQuoteStyle {
107    fn default() -> Self {
108        Self {
109            start: '"',
110            end: '"',
111        }
112    }
113}
114
115impl IdentifierQuoteStyle {
116    /// Double-quote style (PostgreSQL, Oracle, standard SQL)
117    pub const DOUBLE_QUOTE: Self = Self {
118        start: '"',
119        end: '"',
120    };
121    /// Backtick style (MySQL, BigQuery, Spark, Hive)
122    pub const BACKTICK: Self = Self {
123        start: '`',
124        end: '`',
125    };
126    /// Square bracket style (TSQL, SQLite)
127    pub const BRACKET: Self = Self {
128        start: '[',
129        end: ']',
130    };
131}
132
133/// Configuration for the SQL [`Generator`].
134///
135/// This is a comprehensive port of the Python sqlglot `Generator` class attributes.
136/// It controls every aspect of SQL output: formatting, quoting, dialect-specific
137/// syntax, feature support flags, and more.
138///
139/// Most users should start from `GeneratorConfig::default()` and override only the
140/// fields they need. Dialect-specific presets are applied automatically when
141/// `dialect` is set via the higher-level transpilation API.
142///
143/// # Key fields
144///
145/// | Field | Default | Purpose |
146/// |-------|---------|---------|
147/// | `dialect` | `None` | Target SQL dialect (e.g. PostgreSQL, MySQL, BigQuery) |
148/// | `pretty` | `false` | Enable multi-line, indented output |
149/// | `indent` | `"  "` | Indentation string used when `pretty` is true |
150/// | `max_text_width` | `80` | Soft line-width limit for pretty-printing |
151/// | `normalize_functions` | `Upper` | Function name casing (`Upper`, `Lower`, `None`) |
152/// | `identifier_quote_style` | `"…"` | Quote characters for identifiers |
153/// | `uppercase_keywords` | `true` | Whether SQL keywords are upper-cased |
154#[derive(Debug, Clone)]
155pub struct GeneratorConfig {
156    // ===== Basic formatting =====
157    /// Pretty print with indentation
158    pub pretty: bool,
159    /// Indentation string (default 2 spaces)
160    pub indent: String,
161    /// Maximum text width before wrapping (default 80)
162    pub max_text_width: usize,
163    /// Quote identifier style (deprecated, use identifier_quote_style instead)
164    pub identifier_quote: char,
165    /// Identifier quote style with separate start/end characters
166    pub identifier_quote_style: IdentifierQuoteStyle,
167    /// Uppercase keywords
168    pub uppercase_keywords: bool,
169    /// Normalize identifiers to lowercase when generating
170    pub normalize_identifiers: bool,
171    /// Dialect type for dialect-specific generation
172    pub dialect: Option<crate::dialects::DialectType>,
173    /// Source dialect type (used during transpilation to distinguish identity vs cross-dialect)
174    pub source_dialect: Option<crate::dialects::DialectType>,
175    /// How to output function names (UPPER, lower, or as-is)
176    pub normalize_functions: NormalizeFunctions,
177    /// String escape character
178    pub string_escape: char,
179    /// Whether identifiers are case-sensitive
180    pub case_sensitive_identifiers: bool,
181    /// Whether unquoted identifiers can start with a digit
182    pub identifiers_can_start_with_digit: bool,
183    /// Whether to always quote identifiers regardless of reserved keyword status
184    /// Used by dialects like Athena/Presto that prefer quoted identifiers
185    pub always_quote_identifiers: bool,
186    /// How to render negated IN predicates in generic output.
187    pub not_in_style: NotInStyle,
188
189    // ===== Null handling =====
190    /// Whether null ordering (NULLS FIRST/LAST) is supported in ORDER BY
191    /// True: Full Support, false: No support
192    pub null_ordering_supported: bool,
193    /// Whether ignore nulls is inside the agg or outside
194    /// FIRST(x IGNORE NULLS) OVER vs FIRST(x) IGNORE NULLS OVER
195    pub ignore_nulls_in_func: bool,
196    /// Whether the NVL2 function is supported
197    pub nvl2_supported: bool,
198
199    // ===== Limit/Fetch =====
200    /// How to output LIMIT clauses
201    pub limit_fetch_style: LimitFetchStyle,
202    /// Whether to generate the limit as TOP <value> instead of LIMIT <value>
203    pub limit_is_top: bool,
204    /// Whether limit and fetch allows expressions or just literals
205    pub limit_only_literals: bool,
206
207    // ===== Interval =====
208    /// Whether INTERVAL uses single quoted string ('1 day' vs 1 DAY)
209    pub single_string_interval: bool,
210    /// Whether the plural form of date parts (e.g., "days") is supported in INTERVALs
211    pub interval_allows_plural_form: bool,
212
213    // ===== CTE =====
214    /// Whether WITH RECURSIVE keyword is required (vs just WITH for recursive CTEs)
215    pub cte_recursive_keyword_required: bool,
216
217    // ===== VALUES =====
218    /// Whether VALUES can be used as a table source
219    pub values_as_table: bool,
220    /// Wrap derived values in parens (standard but Spark doesn't support)
221    pub wrap_derived_values: bool,
222
223    // ===== TABLESAMPLE =====
224    /// Keyword for TABLESAMPLE seed: "SEED" or "REPEATABLE"
225    pub tablesample_seed_keyword: &'static str,
226    /// Whether parentheses are required around the table sample's expression
227    pub tablesample_requires_parens: bool,
228    /// Whether a table sample clause's size needs to be followed by ROWS keyword
229    pub tablesample_size_is_rows: bool,
230    /// The keyword(s) to use when generating a sample clause
231    pub tablesample_keywords: &'static str,
232    /// Whether the TABLESAMPLE clause supports a method name, like BERNOULLI
233    pub tablesample_with_method: bool,
234    /// Whether the table alias comes after tablesample (Oracle, Hive)
235    pub alias_post_tablesample: bool,
236
237    // ===== Aggregate =====
238    /// Whether aggregate FILTER (WHERE ...) is supported
239    pub aggregate_filter_supported: bool,
240    /// Whether DISTINCT can be followed by multiple args in an AggFunc
241    pub multi_arg_distinct: bool,
242    /// Whether ANY/ALL quantifiers have no space before `(`: `ANY(` vs `ANY (`
243    pub quantified_no_paren_space: bool,
244    /// Whether MEDIAN(expr) is supported; if not, generates PERCENTILE_CONT
245    pub supports_median: bool,
246
247    // ===== SELECT =====
248    /// Whether SELECT ... INTO is supported
249    pub supports_select_into: bool,
250    /// Whether locking reads (SELECT ... FOR UPDATE/SHARE) are supported
251    pub locking_reads_supported: bool,
252
253    // ===== Table/Join =====
254    /// Whether a table is allowed to be renamed with a db
255    pub rename_table_with_db: bool,
256    /// Whether JOIN sides (LEFT, RIGHT) are supported with SEMI/ANTI join kinds
257    pub semi_anti_join_with_side: bool,
258    /// Whether named columns are allowed in table aliases
259    pub supports_table_alias_columns: bool,
260    /// Whether join hints should be generated
261    pub join_hints: bool,
262    /// Whether table hints should be generated
263    pub table_hints: bool,
264    /// Whether query hints should be generated
265    pub query_hints: bool,
266    /// What kind of separator to use for query hints
267    pub query_hint_sep: &'static str,
268    /// Whether Oracle-style (+) join markers are supported (Oracle, Exasol)
269    pub supports_column_join_marks: bool,
270
271    // ===== DDL =====
272    /// Whether CREATE INDEX USING method should have no space before column parens
273    /// true: `USING btree(col)`, false: `USING btree (col)`
274    pub index_using_no_space: bool,
275    /// Whether UNLOGGED tables can be created
276    pub supports_unlogged_tables: bool,
277    /// Whether CREATE TABLE LIKE statement is supported
278    pub supports_create_table_like: bool,
279    /// Whether the LikeProperty needs to be inside the schema clause
280    pub like_property_inside_schema: bool,
281    /// Whether the word COLUMN is included when adding a column with ALTER TABLE
282    pub alter_table_include_column_keyword: bool,
283    /// Whether CREATE TABLE .. COPY .. is supported (false = CLONE instead)
284    pub supports_table_copy: bool,
285    /// The syntax to use when altering the type of a column
286    pub alter_set_type: &'static str,
287    /// Whether to wrap <props> in AlterSet, e.g., ALTER ... SET (<props>)
288    pub alter_set_wrapped: bool,
289
290    // ===== Timestamp/Timezone =====
291    /// Whether TIMESTAMP WITH TIME ZONE is used (vs TIMESTAMPTZ)
292    pub tz_to_with_time_zone: bool,
293    /// Whether CONVERT_TIMEZONE() is supported
294    pub supports_convert_timezone: bool,
295
296    // ===== JSON =====
297    /// Whether the JSON extraction operators expect a value of type JSON
298    pub json_type_required_for_extraction: bool,
299    /// Whether bracketed keys like ["foo"] are supported in JSON paths
300    pub json_path_bracketed_key_supported: bool,
301    /// Whether to escape keys using single quotes in JSON paths
302    pub json_path_single_quote_escape: bool,
303    /// Whether to quote the generated expression of JsonPath
304    pub quote_json_path: bool,
305    /// What delimiter to use for separating JSON key/value pairs
306    pub json_key_value_pair_sep: &'static str,
307
308    // ===== COPY =====
309    /// Whether parameters from COPY statement are wrapped in parentheses
310    pub copy_params_are_wrapped: bool,
311    /// Whether values of params are set with "=" token or empty space
312    pub copy_params_eq_required: bool,
313    /// Whether COPY statement has INTO keyword
314    pub copy_has_into_keyword: bool,
315
316    // ===== Window functions =====
317    /// Whether EXCLUDE in window specification is supported
318    pub supports_window_exclude: bool,
319    /// UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
320    pub unnest_with_ordinality: bool,
321    /// Whether window frame keywords (ROWS/RANGE/GROUPS, PRECEDING/FOLLOWING) should be lowercase
322    /// Exasol uses lowercase for these specific keywords
323    pub lowercase_window_frame_keywords: bool,
324    /// Whether to normalize single-bound window frames to BETWEEN form
325    /// e.g., ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
326    pub normalize_window_frame_between: bool,
327
328    // ===== Array =====
329    /// Whether ARRAY_CONCAT can be generated with varlen args
330    pub array_concat_is_var_len: bool,
331    /// Whether exp.ArraySize should generate the dimension arg too
332    /// None -> Doesn't support, false -> optional, true -> required
333    pub array_size_dim_required: Option<bool>,
334    /// Whether any(f(x) for x in array) can be implemented
335    pub can_implement_array_any: bool,
336    /// Function used for array size
337    pub array_size_name: &'static str,
338
339    // ===== BETWEEN =====
340    /// Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN
341    pub supports_between_flags: bool,
342
343    // ===== Boolean =====
344    /// Whether comparing against booleans (e.g. x IS TRUE) is supported
345    pub is_bool_allowed: bool,
346    /// Whether conditions require booleans WHERE x = 0 vs WHERE x
347    pub ensure_bools: bool,
348
349    // ===== EXTRACT =====
350    /// Whether to generate an unquoted value for EXTRACT's date part argument
351    pub extract_allows_quotes: bool,
352    /// Whether to normalize date parts in EXTRACT
353    pub normalize_extract_date_parts: bool,
354
355    // ===== Other features =====
356    /// Whether the conditional TRY(expression) function is supported
357    pub try_supported: bool,
358    /// Whether the UESCAPE syntax in unicode strings is supported
359    pub supports_uescape: bool,
360    /// Whether the function TO_NUMBER is supported
361    pub supports_to_number: bool,
362    /// Whether CONCAT requires >1 arguments
363    pub supports_single_arg_concat: bool,
364    /// Whether LAST_DAY function supports a date part argument
365    pub last_day_supports_date_part: bool,
366    /// Whether a projection can explode into multiple rows
367    pub supports_exploding_projections: bool,
368    /// Whether UNIX_SECONDS(timestamp) is supported
369    pub supports_unix_seconds: bool,
370    /// Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME
371    pub supports_like_quantifiers: bool,
372    /// Whether multi-argument DECODE(...) function is supported
373    pub supports_decode_case: bool,
374    /// Whether set op modifiers apply to the outer set op or select
375    pub set_op_modifiers: bool,
376    /// Whether FROM is supported in UPDATE statements
377    pub update_statement_supports_from: bool,
378
379    // ===== COLLATE =====
380    /// Whether COLLATE is a function instead of a binary operator
381    pub collate_is_func: bool,
382
383    // ===== INSERT =====
384    /// Whether to include "SET" keyword in "INSERT ... ON DUPLICATE KEY UPDATE"
385    pub duplicate_key_update_with_set: bool,
386    /// INSERT OVERWRITE TABLE x override
387    pub insert_overwrite: &'static str,
388
389    // ===== RETURNING =====
390    /// Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
391    pub returning_end: bool,
392
393    // ===== MERGE =====
394    /// Whether MERGE ... WHEN MATCHED BY SOURCE is allowed
395    pub matched_by_source: bool,
396
397    // ===== CREATE FUNCTION =====
398    /// Whether create function uses an AS before the RETURN
399    pub create_function_return_as: bool,
400    /// Whether to use = instead of DEFAULT for parameter defaults (TSQL style)
401    pub parameter_default_equals: bool,
402
403    // ===== COMPUTED COLUMN =====
404    /// Whether to include the type of a computed column in the CREATE DDL
405    pub computed_column_with_type: bool,
406
407    // ===== UNPIVOT =====
408    /// Whether UNPIVOT aliases are Identifiers (false means they're Literals)
409    pub unpivot_aliases_are_identifiers: bool,
410
411    // ===== STAR =====
412    /// The keyword to use when generating a star projection with excluded columns
413    pub star_except: &'static str,
414
415    // ===== HEX =====
416    /// The HEX function name
417    pub hex_func: &'static str,
418
419    // ===== WITH =====
420    /// The keywords to use when prefixing WITH based properties
421    pub with_properties_prefix: &'static str,
422
423    // ===== PAD =====
424    /// Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional
425    pub pad_fill_pattern_is_required: bool,
426
427    // ===== INDEX =====
428    /// The string used for creating an index on a table
429    pub index_on: &'static str,
430
431    // ===== GROUPING =====
432    /// The separator for grouping sets and rollups
433    pub groupings_sep: &'static str,
434
435    // ===== STRUCT =====
436    /// Delimiters for STRUCT type
437    pub struct_delimiter: (&'static str, &'static str),
438    /// Whether Struct expressions use curly brace notation: {'key': value} (DuckDB)
439    pub struct_curly_brace_notation: bool,
440    /// Whether Array expressions omit the ARRAY keyword: [1, 2] instead of ARRAY[1, 2]
441    pub array_bracket_only: bool,
442    /// Separator between struct field name and type (": " for Hive, " " for others)
443    pub struct_field_sep: &'static str,
444
445    // ===== EXCEPT/INTERSECT =====
446    /// Whether EXCEPT and INTERSECT operations can return duplicates
447    pub except_intersect_support_all_clause: bool,
448
449    // ===== PARAMETERS/PLACEHOLDERS =====
450    /// Parameter token character (@ for TSQL, $ for PostgreSQL)
451    pub parameter_token: &'static str,
452    /// Named placeholder token (: for most, % for PostgreSQL)
453    pub named_placeholder_token: &'static str,
454
455    // ===== DATA TYPES =====
456    /// Whether data types support additional specifiers like CHAR or BYTE (oracle)
457    pub data_type_specifiers_allowed: bool,
458
459    // ===== COMMENT =====
460    /// Whether schema comments use `=` sign (COMMENT='value' vs COMMENT 'value')
461    /// StarRocks and Doris use naked COMMENT syntax without `=`
462    pub schema_comment_with_eq: bool,
463}
464
465impl Default for GeneratorConfig {
466    fn default() -> Self {
467        Self {
468            // ===== Basic formatting =====
469            pretty: false,
470            indent: "  ".to_string(),
471            max_text_width: 80,
472            identifier_quote: '"',
473            identifier_quote_style: IdentifierQuoteStyle::DOUBLE_QUOTE,
474            uppercase_keywords: true,
475            normalize_identifiers: false,
476            dialect: None,
477            source_dialect: None,
478            normalize_functions: NormalizeFunctions::Upper,
479            string_escape: '\'',
480            case_sensitive_identifiers: false,
481            identifiers_can_start_with_digit: false,
482            always_quote_identifiers: false,
483            not_in_style: NotInStyle::Prefix,
484
485            // ===== Null handling =====
486            null_ordering_supported: true,
487            ignore_nulls_in_func: false,
488            nvl2_supported: true,
489
490            // ===== Limit/Fetch =====
491            limit_fetch_style: LimitFetchStyle::Limit,
492            limit_is_top: false,
493            limit_only_literals: false,
494
495            // ===== Interval =====
496            single_string_interval: false,
497            interval_allows_plural_form: true,
498
499            // ===== CTE =====
500            cte_recursive_keyword_required: true,
501
502            // ===== VALUES =====
503            values_as_table: true,
504            wrap_derived_values: true,
505
506            // ===== TABLESAMPLE =====
507            tablesample_seed_keyword: "SEED",
508            tablesample_requires_parens: true,
509            tablesample_size_is_rows: true,
510            tablesample_keywords: "TABLESAMPLE",
511            tablesample_with_method: true,
512            alias_post_tablesample: false,
513
514            // ===== Aggregate =====
515            aggregate_filter_supported: true,
516            multi_arg_distinct: true,
517            quantified_no_paren_space: false,
518            supports_median: true,
519
520            // ===== SELECT =====
521            supports_select_into: false,
522            locking_reads_supported: true,
523
524            // ===== Table/Join =====
525            rename_table_with_db: true,
526            semi_anti_join_with_side: true,
527            supports_table_alias_columns: true,
528            join_hints: true,
529            table_hints: true,
530            query_hints: true,
531            query_hint_sep: ", ",
532            supports_column_join_marks: false,
533
534            // ===== DDL =====
535            index_using_no_space: false,
536            supports_unlogged_tables: false,
537            supports_create_table_like: true,
538            like_property_inside_schema: false,
539            alter_table_include_column_keyword: true,
540            supports_table_copy: true,
541            alter_set_type: "SET DATA TYPE",
542            alter_set_wrapped: false,
543
544            // ===== Timestamp/Timezone =====
545            tz_to_with_time_zone: false,
546            supports_convert_timezone: false,
547
548            // ===== JSON =====
549            json_type_required_for_extraction: false,
550            json_path_bracketed_key_supported: true,
551            json_path_single_quote_escape: false,
552            quote_json_path: true,
553            json_key_value_pair_sep: ":",
554
555            // ===== COPY =====
556            copy_params_are_wrapped: true,
557            copy_params_eq_required: false,
558            copy_has_into_keyword: true,
559
560            // ===== Window functions =====
561            supports_window_exclude: false,
562            unnest_with_ordinality: true,
563            lowercase_window_frame_keywords: false,
564            normalize_window_frame_between: false,
565
566            // ===== Array =====
567            array_concat_is_var_len: true,
568            array_size_dim_required: None,
569            can_implement_array_any: false,
570            array_size_name: "ARRAY_LENGTH",
571
572            // ===== BETWEEN =====
573            supports_between_flags: false,
574
575            // ===== Boolean =====
576            is_bool_allowed: true,
577            ensure_bools: false,
578
579            // ===== EXTRACT =====
580            extract_allows_quotes: true,
581            normalize_extract_date_parts: false,
582
583            // ===== Other features =====
584            try_supported: true,
585            supports_uescape: true,
586            supports_to_number: true,
587            supports_single_arg_concat: true,
588            last_day_supports_date_part: true,
589            supports_exploding_projections: true,
590            supports_unix_seconds: false,
591            supports_like_quantifiers: true,
592            supports_decode_case: true,
593            set_op_modifiers: true,
594            update_statement_supports_from: true,
595
596            // ===== COLLATE =====
597            collate_is_func: false,
598
599            // ===== INSERT =====
600            duplicate_key_update_with_set: true,
601            insert_overwrite: " OVERWRITE TABLE",
602
603            // ===== RETURNING =====
604            returning_end: true,
605
606            // ===== MERGE =====
607            matched_by_source: true,
608
609            // ===== CREATE FUNCTION =====
610            create_function_return_as: true,
611            parameter_default_equals: false,
612
613            // ===== COMPUTED COLUMN =====
614            computed_column_with_type: true,
615
616            // ===== UNPIVOT =====
617            unpivot_aliases_are_identifiers: true,
618
619            // ===== STAR =====
620            star_except: "EXCEPT",
621
622            // ===== HEX =====
623            hex_func: "HEX",
624
625            // ===== WITH =====
626            with_properties_prefix: "WITH",
627
628            // ===== PAD =====
629            pad_fill_pattern_is_required: false,
630
631            // ===== INDEX =====
632            index_on: "ON",
633
634            // ===== GROUPING =====
635            groupings_sep: ",",
636
637            // ===== STRUCT =====
638            struct_delimiter: ("<", ">"),
639            struct_curly_brace_notation: false,
640            array_bracket_only: false,
641            struct_field_sep: " ",
642
643            // ===== EXCEPT/INTERSECT =====
644            except_intersect_support_all_clause: true,
645
646            // ===== PARAMETERS/PLACEHOLDERS =====
647            parameter_token: "@",
648            named_placeholder_token: ":",
649
650            // ===== DATA TYPES =====
651            data_type_specifiers_allowed: false,
652
653            // ===== COMMENT =====
654            schema_comment_with_eq: true,
655        }
656    }
657}
658
659/// SQL reserved keywords that require quoting when used as identifiers
660/// Based on ANSI SQL standards and common dialect-specific reserved words
661mod reserved_keywords {
662    use std::collections::HashSet;
663    use std::sync::LazyLock;
664
665    /// Standard SQL reserved keywords (ANSI SQL:2016)
666    pub static SQL_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
667        [
668            "all",
669            "alter",
670            "and",
671            "any",
672            "array",
673            "as",
674            "asc",
675            "at",
676            "authorization",
677            "begin",
678            "between",
679            "both",
680            "by",
681            "case",
682            "cast",
683            "check",
684            "collate",
685            "column",
686            "commit",
687            "constraint",
688            "create",
689            "cross",
690            "cube",
691            "current",
692            "current_date",
693            "current_time",
694            "current_timestamp",
695            "current_user",
696            "default",
697            "delete",
698            "desc",
699            "distinct",
700            "drop",
701            "else",
702            "end",
703            "escape",
704            "except",
705            "execute",
706            "exists",
707            "external",
708            "false",
709            "fetch",
710            "filter",
711            "for",
712            "foreign",
713            "from",
714            "full",
715            "function",
716            "grant",
717            "group",
718            "grouping",
719            "having",
720            "if",
721            "in",
722            "index",
723            "inner",
724            "insert",
725            "intersect",
726            "interval",
727            "into",
728            "is",
729            "join",
730            "key",
731            "leading",
732            "left",
733            "like",
734            "limit",
735            "local",
736            "localtime",
737            "localtimestamp",
738            "match",
739            "merge",
740            "natural",
741            "no",
742            "not",
743            "null",
744            "of",
745            "offset",
746            "on",
747            "only",
748            "or",
749            "order",
750            "outer",
751            "over",
752            "partition",
753            "primary",
754            "procedure",
755            "range",
756            "references",
757            "right",
758            "rollback",
759            "rollup",
760            "row",
761            "rows",
762            "select",
763            "session_user",
764            "set",
765            "some",
766            "table",
767            "tablesample",
768            "then",
769            "to",
770            "trailing",
771            "true",
772            "truncate",
773            "union",
774            "unique",
775            "unknown",
776            "update",
777            "user",
778            "using",
779            "values",
780            "view",
781            "when",
782            "where",
783            "window",
784            "with",
785        ]
786        .into_iter()
787        .collect()
788    });
789
790    /// BigQuery-specific reserved keywords
791    /// Based on: https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#reserved_keywords
792    pub static BIGQUERY_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
793        let mut set = SQL_RESERVED.clone();
794        set.extend([
795            "assert_rows_modified",
796            "at",
797            "contains",
798            "cube",
799            "current",
800            "define",
801            "enum",
802            "escape",
803            "exclude",
804            "following",
805            "for",
806            "groups",
807            "hash",
808            "ignore",
809            "lateral",
810            "lookup",
811            "new",
812            "no",
813            "nulls",
814            "of",
815            "over",
816            "preceding",
817            "proto",
818            "qualify",
819            "recursive",
820            "respect",
821            "struct",
822            "tablesample",
823            "treat",
824            "unbounded",
825            "unnest",
826            "window",
827            "within",
828        ]);
829        // BigQuery does NOT reserve these keywords - they can be used as identifiers unquoted
830        set.remove("grant");
831        set.remove("key");
832        set.remove("index");
833        set.remove("values");
834        set.remove("table");
835        set
836    });
837
838    /// MySQL-specific reserved keywords
839    pub static MYSQL_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
840        let mut set = SQL_RESERVED.clone();
841        set.extend([
842            "accessible",
843            "add",
844            "analyze",
845            "asensitive",
846            "before",
847            "bigint",
848            "binary",
849            "blob",
850            "call",
851            "cascade",
852            "change",
853            "char",
854            "character",
855            "condition",
856            "continue",
857            "convert",
858            "current_date",
859            "current_time",
860            "current_timestamp",
861            "current_user",
862            "cursor",
863            "database",
864            "databases",
865            "day_hour",
866            "day_microsecond",
867            "day_minute",
868            "day_second",
869            "dec",
870            "decimal",
871            "declare",
872            "delayed",
873            "describe",
874            "deterministic",
875            "distinctrow",
876            "div",
877            "double",
878            "dual",
879            "each",
880            "elseif",
881            "enclosed",
882            "escaped",
883            "exit",
884            "explain",
885            "float",
886            "float4",
887            "float8",
888            "force",
889            "get",
890            "high_priority",
891            "hour_microsecond",
892            "hour_minute",
893            "hour_second",
894            "ignore",
895            "infile",
896            "inout",
897            "insensitive",
898            "int",
899            "int1",
900            "int2",
901            "int3",
902            "int4",
903            "int8",
904            "integer",
905            "iterate",
906            "keys",
907            "kill",
908            "leave",
909            "linear",
910            "lines",
911            "load",
912            "lock",
913            "long",
914            "longblob",
915            "longtext",
916            "loop",
917            "low_priority",
918            "master_ssl_verify_server_cert",
919            "maxvalue",
920            "mediumblob",
921            "mediumint",
922            "mediumtext",
923            "middleint",
924            "minute_microsecond",
925            "minute_second",
926            "mod",
927            "modifies",
928            "no_write_to_binlog",
929            "numeric",
930            "optimize",
931            "option",
932            "optionally",
933            "out",
934            "outfile",
935            "precision",
936            "purge",
937            "read",
938            "reads",
939            "real",
940            "regexp",
941            "release",
942            "rename",
943            "repeat",
944            "replace",
945            "require",
946            "resignal",
947            "restrict",
948            "return",
949            "revoke",
950            "rlike",
951            "schema",
952            "schemas",
953            "second_microsecond",
954            "sensitive",
955            "separator",
956            "show",
957            "signal",
958            "smallint",
959            "spatial",
960            "specific",
961            "sql",
962            "sql_big_result",
963            "sql_calc_found_rows",
964            "sql_small_result",
965            "sqlexception",
966            "sqlstate",
967            "sqlwarning",
968            "ssl",
969            "starting",
970            "straight_join",
971            "terminated",
972            "text",
973            "tinyblob",
974            "tinyint",
975            "tinytext",
976            "trigger",
977            "undo",
978            "unlock",
979            "unsigned",
980            "usage",
981            "utc_date",
982            "utc_time",
983            "utc_timestamp",
984            "varbinary",
985            "varchar",
986            "varcharacter",
987            "varying",
988            "while",
989            "write",
990            "xor",
991            "year_month",
992            "zerofill",
993        ]);
994        set.remove("table");
995        set
996    });
997
998    /// Doris-specific reserved keywords
999    /// Extends MySQL reserved with additional Doris-specific words
1000    pub static DORIS_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1001        let mut set = MYSQL_RESERVED.clone();
1002        set.extend([
1003            "aggregate",
1004            "anti",
1005            "array",
1006            "backend",
1007            "backup",
1008            "begin",
1009            "bitmap",
1010            "boolean",
1011            "broker",
1012            "buckets",
1013            "cached",
1014            "cancel",
1015            "cast",
1016            "catalog",
1017            "charset",
1018            "cluster",
1019            "collation",
1020            "columns",
1021            "comment",
1022            "commit",
1023            "config",
1024            "connection",
1025            "count",
1026            "current",
1027            "data",
1028            "date",
1029            "datetime",
1030            "day",
1031            "deferred",
1032            "distributed",
1033            "dynamic",
1034            "enable",
1035            "end",
1036            "events",
1037            "export",
1038            "external",
1039            "fields",
1040            "first",
1041            "follower",
1042            "format",
1043            "free",
1044            "frontend",
1045            "full",
1046            "functions",
1047            "global",
1048            "grants",
1049            "hash",
1050            "help",
1051            "hour",
1052            "install",
1053            "intermediate",
1054            "json",
1055            "label",
1056            "last",
1057            "less",
1058            "level",
1059            "link",
1060            "local",
1061            "location",
1062            "max",
1063            "merge",
1064            "min",
1065            "minute",
1066            "modify",
1067            "month",
1068            "name",
1069            "names",
1070            "negative",
1071            "nulls",
1072            "observer",
1073            "offset",
1074            "only",
1075            "open",
1076            "overwrite",
1077            "password",
1078            "path",
1079            "plan",
1080            "plugin",
1081            "plugins",
1082            "policy",
1083            "process",
1084            "properties",
1085            "property",
1086            "query",
1087            "quota",
1088            "recover",
1089            "refresh",
1090            "repair",
1091            "replica",
1092            "repository",
1093            "resource",
1094            "restore",
1095            "resume",
1096            "role",
1097            "roles",
1098            "rollback",
1099            "rollup",
1100            "routine",
1101            "sample",
1102            "second",
1103            "semi",
1104            "session",
1105            "signed",
1106            "snapshot",
1107            "start",
1108            "stats",
1109            "status",
1110            "stop",
1111            "stream",
1112            "string",
1113            "sum",
1114            "tables",
1115            "tablet",
1116            "temporary",
1117            "text",
1118            "timestamp",
1119            "transaction",
1120            "trash",
1121            "trim",
1122            "truncate",
1123            "type",
1124            "user",
1125            "value",
1126            "variables",
1127            "verbose",
1128            "version",
1129            "view",
1130            "warnings",
1131            "week",
1132            "work",
1133            "year",
1134        ]);
1135        set
1136    });
1137
1138    /// PostgreSQL-specific reserved keywords
1139    pub static POSTGRES_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1140        let mut set = SQL_RESERVED.clone();
1141        set.extend([
1142            "analyse",
1143            "analyze",
1144            "asymmetric",
1145            "binary",
1146            "collation",
1147            "concurrently",
1148            "current_catalog",
1149            "current_role",
1150            "current_schema",
1151            "deferrable",
1152            "do",
1153            "freeze",
1154            "ilike",
1155            "initially",
1156            "isnull",
1157            "lateral",
1158            "notnull",
1159            "placing",
1160            "returning",
1161            "similar",
1162            "symmetric",
1163            "variadic",
1164            "verbose",
1165        ]);
1166        // PostgreSQL doesn't require quoting for these keywords
1167        set.remove("default");
1168        set.remove("interval");
1169        set.remove("match");
1170        set.remove("offset");
1171        set.remove("table");
1172        set
1173    });
1174
1175    /// Redshift-specific reserved keywords
1176    /// Based on: https://docs.aws.amazon.com/redshift/latest/dg/r_pg_keywords.html
1177    /// Note: `index` is NOT reserved in Redshift (unlike PostgreSQL)
1178    pub static REDSHIFT_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1179        [
1180            "aes128",
1181            "aes256",
1182            "all",
1183            "allowoverwrite",
1184            "analyse",
1185            "analyze",
1186            "and",
1187            "any",
1188            "array",
1189            "as",
1190            "asc",
1191            "authorization",
1192            "az64",
1193            "backup",
1194            "between",
1195            "binary",
1196            "blanksasnull",
1197            "both",
1198            "bytedict",
1199            "bzip2",
1200            "case",
1201            "cast",
1202            "check",
1203            "collate",
1204            "column",
1205            "constraint",
1206            "create",
1207            "credentials",
1208            "cross",
1209            "current_date",
1210            "current_time",
1211            "current_timestamp",
1212            "current_user",
1213            "current_user_id",
1214            "default",
1215            "deferrable",
1216            "deflate",
1217            "defrag",
1218            "delta",
1219            "delta32k",
1220            "desc",
1221            "disable",
1222            "distinct",
1223            "do",
1224            "else",
1225            "emptyasnull",
1226            "enable",
1227            "encode",
1228            "encrypt",
1229            "encryption",
1230            "end",
1231            "except",
1232            "explicit",
1233            "false",
1234            "for",
1235            "foreign",
1236            "freeze",
1237            "from",
1238            "full",
1239            "globaldict256",
1240            "globaldict64k",
1241            "grant",
1242            "group",
1243            "gzip",
1244            "having",
1245            "identity",
1246            "ignore",
1247            "ilike",
1248            "in",
1249            "initially",
1250            "inner",
1251            "intersect",
1252            "interval",
1253            "into",
1254            "is",
1255            "isnull",
1256            "join",
1257            "leading",
1258            "left",
1259            "like",
1260            "limit",
1261            "localtime",
1262            "localtimestamp",
1263            "lun",
1264            "luns",
1265            "lzo",
1266            "lzop",
1267            "minus",
1268            "mostly16",
1269            "mostly32",
1270            "mostly8",
1271            "natural",
1272            "new",
1273            "not",
1274            "notnull",
1275            "null",
1276            "nulls",
1277            "off",
1278            "offline",
1279            "offset",
1280            "oid",
1281            "old",
1282            "on",
1283            "only",
1284            "open",
1285            "or",
1286            "order",
1287            "outer",
1288            "overlaps",
1289            "parallel",
1290            "partition",
1291            "percent",
1292            "permissions",
1293            "pivot",
1294            "placing",
1295            "primary",
1296            "raw",
1297            "readratio",
1298            "recover",
1299            "references",
1300            "rejectlog",
1301            "resort",
1302            "respect",
1303            "restore",
1304            "right",
1305            "select",
1306            "session_user",
1307            "similar",
1308            "snapshot",
1309            "some",
1310            "sysdate",
1311            "system",
1312            "table",
1313            "tag",
1314            "tdes",
1315            "text255",
1316            "text32k",
1317            "then",
1318            "timestamp",
1319            "to",
1320            "top",
1321            "trailing",
1322            "true",
1323            "truncatecolumns",
1324            "type",
1325            "union",
1326            "unique",
1327            "unnest",
1328            "unpivot",
1329            "user",
1330            "using",
1331            "verbose",
1332            "wallet",
1333            "when",
1334            "where",
1335            "with",
1336            "without",
1337        ]
1338        .into_iter()
1339        .collect()
1340    });
1341
1342    /// DuckDB-specific reserved keywords
1343    pub static DUCKDB_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1344        let mut set = POSTGRES_RESERVED.clone();
1345        set.extend([
1346            "anti",
1347            "asof",
1348            "columns",
1349            "describe",
1350            "groups",
1351            "macro",
1352            "pivot",
1353            "pivot_longer",
1354            "pivot_wider",
1355            "qualify",
1356            "replace",
1357            "respect",
1358            "semi",
1359            "show",
1360            "table",
1361            "unpivot",
1362        ]);
1363        set.remove("at");
1364        set.remove("key");
1365        set.remove("row");
1366        set
1367    });
1368
1369    /// Presto/Trino/Athena-specific reserved keywords
1370    pub static PRESTO_TRINO_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1371        let mut set = SQL_RESERVED.clone();
1372        set.extend([
1373            "alter",
1374            "and",
1375            "as",
1376            "between",
1377            "by",
1378            "case",
1379            "cast",
1380            "constraint",
1381            "create",
1382            "cross",
1383            "cube",
1384            "current_catalog",
1385            "current_date",
1386            "current_path",
1387            "current_role",
1388            "current_schema",
1389            "current_time",
1390            "current_timestamp",
1391            "current_user",
1392            "deallocate",
1393            "delete",
1394            "describe",
1395            "distinct",
1396            "drop",
1397            "else",
1398            "end",
1399            "escape",
1400            "except",
1401            "execute",
1402            "exists",
1403            "extract",
1404            "false",
1405            "for",
1406            "from",
1407            "full",
1408            "group",
1409            "grouping",
1410            "having",
1411            "in",
1412            "inner",
1413            "insert",
1414            "intersect",
1415            "into",
1416            "is",
1417            "join",
1418            "json_array",
1419            "json_exists",
1420            "json_object",
1421            "json_query",
1422            "json_table",
1423            "json_value",
1424            "left",
1425            "like",
1426            "listagg",
1427            "localtime",
1428            "localtimestamp",
1429            "natural",
1430            "normalize",
1431            "not",
1432            "null",
1433            "on",
1434            "or",
1435            "order",
1436            "outer",
1437            "prepare",
1438            "recursive",
1439            "right",
1440            "rollup",
1441            "select",
1442            "skip",
1443            "table",
1444            "then",
1445            "trim",
1446            "true",
1447            "uescape",
1448            "union",
1449            "unnest",
1450            "using",
1451            "values",
1452            "when",
1453            "where",
1454            "with",
1455        ]);
1456        // Match sqlglot behavior for Presto/Trino: KEY does not require identifier quoting.
1457        set.remove("key");
1458        set
1459    });
1460
1461    /// StarRocks-specific reserved keywords
1462    /// Based on: https://docs.starrocks.io/docs/sql-reference/sql-statements/keywords/#reserved-keywords
1463    pub static STARROCKS_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1464        [
1465            "add",
1466            "all",
1467            "alter",
1468            "analyze",
1469            "and",
1470            "array",
1471            "as",
1472            "asc",
1473            "between",
1474            "bigint",
1475            "bitmap",
1476            "both",
1477            "by",
1478            "case",
1479            "char",
1480            "character",
1481            "check",
1482            "collate",
1483            "column",
1484            "compaction",
1485            "convert",
1486            "create",
1487            "cross",
1488            "cube",
1489            "current_date",
1490            "current_role",
1491            "current_time",
1492            "current_timestamp",
1493            "current_user",
1494            "database",
1495            "databases",
1496            "decimal",
1497            "decimalv2",
1498            "decimal32",
1499            "decimal64",
1500            "decimal128",
1501            "default",
1502            "deferred",
1503            "delete",
1504            "dense_rank",
1505            "desc",
1506            "describe",
1507            "distinct",
1508            "double",
1509            "drop",
1510            "dual",
1511            "else",
1512            "except",
1513            "exists",
1514            "explain",
1515            "false",
1516            "first_value",
1517            "float",
1518            "for",
1519            "force",
1520            "from",
1521            "full",
1522            "function",
1523            "grant",
1524            "group",
1525            "grouping",
1526            "grouping_id",
1527            "groups",
1528            "having",
1529            "hll",
1530            "host",
1531            "if",
1532            "ignore",
1533            "immediate",
1534            "in",
1535            "index",
1536            "infile",
1537            "inner",
1538            "insert",
1539            "int",
1540            "integer",
1541            "intersect",
1542            "into",
1543            "is",
1544            "join",
1545            "json",
1546            "key",
1547            "keys",
1548            "kill",
1549            "lag",
1550            "largeint",
1551            "last_value",
1552            "lateral",
1553            "lead",
1554            "left",
1555            "like",
1556            "limit",
1557            "load",
1558            "localtime",
1559            "localtimestamp",
1560            "maxvalue",
1561            "minus",
1562            "mod",
1563            "not",
1564            "ntile",
1565            "null",
1566            "on",
1567            "or",
1568            "order",
1569            "outer",
1570            "outfile",
1571            "over",
1572            "partition",
1573            "percentile",
1574            "primary",
1575            "procedure",
1576            "qualify",
1577            "range",
1578            "rank",
1579            "read",
1580            "regexp",
1581            "release",
1582            "rename",
1583            "replace",
1584            "revoke",
1585            "right",
1586            "rlike",
1587            "row",
1588            "row_number",
1589            "rows",
1590            "schema",
1591            "schemas",
1592            "select",
1593            "set",
1594            "set_var",
1595            "show",
1596            "smallint",
1597            "system",
1598            "table",
1599            "terminated",
1600            "text",
1601            "then",
1602            "tinyint",
1603            "to",
1604            "true",
1605            "union",
1606            "unique",
1607            "unsigned",
1608            "update",
1609            "use",
1610            "using",
1611            "values",
1612            "varchar",
1613            "when",
1614            "where",
1615            "with",
1616        ]
1617        .into_iter()
1618        .collect()
1619    });
1620
1621    /// SingleStore-specific reserved keywords
1622    /// Based on: https://docs.singlestore.com/cloud/reference/sql-reference/restricted-keywords/list-of-restricted-keywords/
1623    pub static SINGLESTORE_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1624        let mut set = MYSQL_RESERVED.clone();
1625        set.extend([
1626            // Additional SingleStore reserved keywords from Python sqlglot
1627            // NOTE: "all" is excluded because ORDER BY ALL needs ALL unquoted
1628            "abs",
1629            "account",
1630            "acos",
1631            "adddate",
1632            "addtime",
1633            "admin",
1634            "aes_decrypt",
1635            "aes_encrypt",
1636            "aggregate",
1637            "aggregates",
1638            "aggregator",
1639            "anti_join",
1640            "any_value",
1641            "approx_count_distinct",
1642            "approx_percentile",
1643            "arrange",
1644            "arrangement",
1645            "asin",
1646            "atan",
1647            "atan2",
1648            "attach",
1649            "autostats",
1650            "avro",
1651            "background",
1652            "backup",
1653            "batch",
1654            "batches",
1655            "boot_strapping",
1656            "ceil",
1657            "ceiling",
1658            "coercibility",
1659            "columnar",
1660            "columnstore",
1661            "compile",
1662            "concurrent",
1663            "connection_id",
1664            "cos",
1665            "cot",
1666            "current_security_groups",
1667            "current_security_roles",
1668            "dayname",
1669            "dayofmonth",
1670            "dayofweek",
1671            "dayofyear",
1672            "degrees",
1673            "dot_product",
1674            "dump",
1675            "durability",
1676            "earliest",
1677            "echo",
1678            "election",
1679            "euclidean_distance",
1680            "exp",
1681            "extractor",
1682            "extractors",
1683            "floor",
1684            "foreground",
1685            "found_rows",
1686            "from_base64",
1687            "from_days",
1688            "from_unixtime",
1689            "fs",
1690            "fulltext",
1691            "gc",
1692            "gcs",
1693            "geography",
1694            "geography_area",
1695            "geography_contains",
1696            "geography_distance",
1697            "geography_intersects",
1698            "geography_latitude",
1699            "geography_length",
1700            "geography_longitude",
1701            "geographypoint",
1702            "geography_point",
1703            "geography_within_distance",
1704            "geometry",
1705            "geometry_area",
1706            "geometry_contains",
1707            "geometry_distance",
1708            "geometry_filter",
1709            "geometry_intersects",
1710            "geometry_length",
1711            "geometrypoint",
1712            "geometry_point",
1713            "geometry_within_distance",
1714            "geometry_x",
1715            "geometry_y",
1716            "greatest",
1717            "groups",
1718            "group_concat",
1719            "gzip",
1720            "hdfs",
1721            "hex",
1722            "highlight",
1723            "ifnull",
1724            "ilike",
1725            "inet_aton",
1726            "inet_ntoa",
1727            "inet6_aton",
1728            "inet6_ntoa",
1729            "initcap",
1730            "instr",
1731            "interpreter_mode",
1732            "isnull",
1733            "json",
1734            "json_agg",
1735            "json_array_contains_double",
1736            "json_array_contains_json",
1737            "json_array_contains_string",
1738            "json_delete_key",
1739            "json_extract_double",
1740            "json_extract_json",
1741            "json_extract_string",
1742            "json_extract_bigint",
1743            "json_get_type",
1744            "json_length",
1745            "json_set_double",
1746            "json_set_json",
1747            "json_set_string",
1748            "kafka",
1749            "lag",
1750            "last_day",
1751            "last_insert_id",
1752            "latest",
1753            "lcase",
1754            "lead",
1755            "leaf",
1756            "least",
1757            "leaves",
1758            "length",
1759            "license",
1760            "links",
1761            "llvm",
1762            "ln",
1763            "load",
1764            "locate",
1765            "log",
1766            "log10",
1767            "log2",
1768            "lpad",
1769            "lz4",
1770            "management",
1771            "match",
1772            "mbc",
1773            "md5",
1774            "median",
1775            "memsql",
1776            "memsql_deserialize",
1777            "memsql_serialize",
1778            "metadata",
1779            "microsecond",
1780            "minute",
1781            "model",
1782            "monthname",
1783            "months_between",
1784            "mpl",
1785            "namespace",
1786            "node",
1787            "noparam",
1788            "now",
1789            "nth_value",
1790            "ntile",
1791            "nullcols",
1792            "nullif",
1793            "object",
1794            "octet_length",
1795            "offsets",
1796            "online",
1797            "optimizer",
1798            "orphan",
1799            "parquet",
1800            "partitions",
1801            "pause",
1802            "percentile_cont",
1803            "percentile_disc",
1804            "periodic",
1805            "persisted",
1806            "pi",
1807            "pipeline",
1808            "pipelines",
1809            "plancache",
1810            "plugins",
1811            "pool",
1812            "pools",
1813            "pow",
1814            "power",
1815            "process",
1816            "processlist",
1817            "profile",
1818            "profiles",
1819            "quarter",
1820            "queries",
1821            "query",
1822            "radians",
1823            "rand",
1824            "record",
1825            "reduce",
1826            "redundancy",
1827            "regexp_match",
1828            "regexp_substr",
1829            "remote",
1830            "replication",
1831            "resource",
1832            "resource_pool",
1833            "restore",
1834            "retry",
1835            "role",
1836            "roles",
1837            "round",
1838            "rpad",
1839            "rtrim",
1840            "running",
1841            "s3",
1842            "scalar",
1843            "sec_to_time",
1844            "second",
1845            "security_lists_intersect",
1846            "semi_join",
1847            "sha",
1848            "sha1",
1849            "sha2",
1850            "shard",
1851            "sharded",
1852            "sharded_id",
1853            "sigmoid",
1854            "sign",
1855            "sin",
1856            "skip",
1857            "sleep",
1858            "snapshot",
1859            "soname",
1860            "sparse",
1861            "spatial_check_index",
1862            "split",
1863            "sqrt",
1864            "standalone",
1865            "std",
1866            "stddev",
1867            "stddev_pop",
1868            "stddev_samp",
1869            "stop",
1870            "str_to_date",
1871            "subdate",
1872            "substr",
1873            "substring_index",
1874            "success",
1875            "synchronize",
1876            "table_checksum",
1877            "tan",
1878            "task",
1879            "timediff",
1880            "time_bucket",
1881            "time_format",
1882            "time_to_sec",
1883            "timestampadd",
1884            "timestampdiff",
1885            "to_base64",
1886            "to_char",
1887            "to_date",
1888            "to_days",
1889            "to_json",
1890            "to_number",
1891            "to_seconds",
1892            "to_timestamp",
1893            "tracelogs",
1894            "transform",
1895            "trim",
1896            "trunc",
1897            "truncate",
1898            "ucase",
1899            "unhex",
1900            "unix_timestamp",
1901            "utc_date",
1902            "utc_time",
1903            "utc_timestamp",
1904            "vacuum",
1905            "variance",
1906            "var_pop",
1907            "var_samp",
1908            "vector_sub",
1909            "voting",
1910            "week",
1911            "weekday",
1912            "weekofyear",
1913            "workload",
1914            "year",
1915        ]);
1916        // Remove "all" because ORDER BY ALL needs ALL unquoted
1917        set.remove("all");
1918        set
1919    });
1920
1921    /// SQLite-specific reserved keywords
1922    /// SQLite has a very minimal set of reserved keywords - most things can be used as identifiers unquoted
1923    /// Reference: https://www.sqlite.org/lang_keywords.html
1924    pub static SQLITE_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1925        // SQLite only truly reserves these - everything else can be used as identifier unquoted
1926        [
1927            "abort",
1928            "action",
1929            "add",
1930            "after",
1931            "all",
1932            "alter",
1933            "always",
1934            "analyze",
1935            "and",
1936            "as",
1937            "asc",
1938            "attach",
1939            "autoincrement",
1940            "before",
1941            "begin",
1942            "between",
1943            "by",
1944            "cascade",
1945            "case",
1946            "cast",
1947            "check",
1948            "collate",
1949            "column",
1950            "commit",
1951            "conflict",
1952            "constraint",
1953            "create",
1954            "cross",
1955            "current",
1956            "current_date",
1957            "current_time",
1958            "current_timestamp",
1959            "database",
1960            "default",
1961            "deferrable",
1962            "deferred",
1963            "delete",
1964            "desc",
1965            "detach",
1966            "distinct",
1967            "do",
1968            "drop",
1969            "each",
1970            "else",
1971            "end",
1972            "escape",
1973            "except",
1974            "exclude",
1975            "exclusive",
1976            "exists",
1977            "explain",
1978            "fail",
1979            "filter",
1980            "first",
1981            "following",
1982            "for",
1983            "foreign",
1984            "from",
1985            "full",
1986            "generated",
1987            "glob",
1988            "group",
1989            "groups",
1990            "having",
1991            "if",
1992            "ignore",
1993            "immediate",
1994            "in",
1995            "index",
1996            "indexed",
1997            "initially",
1998            "inner",
1999            "insert",
2000            "instead",
2001            "intersect",
2002            "into",
2003            "is",
2004            "isnull",
2005            "join",
2006            "key",
2007            "last",
2008            "left",
2009            "like",
2010            "limit",
2011            "natural",
2012            "no",
2013            "not",
2014            "nothing",
2015            "notnull",
2016            "null",
2017            "nulls",
2018            "of",
2019            "offset",
2020            "on",
2021            "or",
2022            "order",
2023            "others",
2024            "outer",
2025            "partition",
2026            "plan",
2027            "pragma",
2028            "preceding",
2029            "primary",
2030            "query",
2031            "raise",
2032            "range",
2033            "recursive",
2034            "references",
2035            "regexp",
2036            "reindex",
2037            "release",
2038            "rename",
2039            "replace",
2040            "restrict",
2041            "returning",
2042            "right",
2043            "rollback",
2044            "row",
2045            "rows",
2046            "savepoint",
2047            "select",
2048            "set",
2049            "table",
2050            "temp",
2051            "temporary",
2052            "then",
2053            "ties",
2054            "to",
2055            "transaction",
2056            "trigger",
2057            "unbounded",
2058            "union",
2059            "unique",
2060            "update",
2061            "using",
2062            "vacuum",
2063            "values",
2064            "view",
2065            "virtual",
2066            "when",
2067            "where",
2068            "window",
2069            "with",
2070            "without",
2071        ]
2072        .into_iter()
2073        .collect()
2074    });
2075}
2076
2077impl Generator {
2078    /// Create a new generator with the default configuration.
2079    ///
2080    /// Equivalent to `Generator::with_config(GeneratorConfig::default())`.
2081    /// Uses uppercase keywords, double-quote identifier quoting, no pretty-printing,
2082    /// and no dialect-specific transformations.
2083    pub fn new() -> Self {
2084        Self::with_config(GeneratorConfig::default())
2085    }
2086
2087    /// Create a generator with a custom [`GeneratorConfig`].
2088    ///
2089    /// Use this when you need dialect-specific output, pretty-printing, or other
2090    /// non-default settings.
2091    pub fn with_config(config: GeneratorConfig) -> Self {
2092        Self {
2093            config,
2094            output: String::new(),
2095            indent_level: 0,
2096            athena_hive_context: false,
2097            sqlite_inline_pk_columns: std::collections::HashSet::new(),
2098            merge_strip_qualifiers: Vec::new(),
2099            clickhouse_nullable_depth: 0,
2100        }
2101    }
2102
2103    /// Add column aliases to a query expression for TSQL SELECT INTO.
2104    /// This ensures that unaliased columns get explicit aliases (e.g., `a` -> `a AS a`).
2105    /// Recursively processes all SELECT expressions in the query tree.
2106    fn add_column_aliases_to_query(expr: Expression) -> Expression {
2107        match expr {
2108            Expression::Select(mut select) => {
2109                // Add aliases to all select expressions that don't already have them
2110                select.expressions = select
2111                    .expressions
2112                    .into_iter()
2113                    .map(|e| Self::add_alias_to_expression(e))
2114                    .collect();
2115
2116                // Recursively process subqueries in FROM clause
2117                if let Some(ref mut from) = select.from {
2118                    from.expressions = from
2119                        .expressions
2120                        .iter()
2121                        .cloned()
2122                        .map(|e| Self::add_column_aliases_to_query(e))
2123                        .collect();
2124                }
2125
2126                Expression::Select(select)
2127            }
2128            Expression::Subquery(mut sq) => {
2129                sq.this = Self::add_column_aliases_to_query(sq.this);
2130                Expression::Subquery(sq)
2131            }
2132            Expression::Paren(mut p) => {
2133                p.this = Self::add_column_aliases_to_query(p.this);
2134                Expression::Paren(p)
2135            }
2136            // For other expressions (Union, Intersect, etc.), pass through
2137            other => other,
2138        }
2139    }
2140
2141    /// Add an alias to a single select expression if it doesn't already have one.
2142    /// Returns the expression with alias (e.g., `a` -> `a AS a`).
2143    fn add_alias_to_expression(expr: Expression) -> Expression {
2144        use crate::expressions::Alias;
2145
2146        match &expr {
2147            // Already aliased - just return it
2148            Expression::Alias(_) => expr,
2149
2150            // Column reference: add alias from column name
2151            Expression::Column(col) => Expression::Alias(Box::new(Alias {
2152                this: expr.clone(),
2153                alias: col.name.clone(),
2154                column_aliases: Vec::new(),
2155                pre_alias_comments: Vec::new(),
2156                trailing_comments: Vec::new(),
2157            })),
2158
2159            // Identifier: add alias from identifier name
2160            Expression::Identifier(ident) => Expression::Alias(Box::new(Alias {
2161                this: expr.clone(),
2162                alias: ident.clone(),
2163                column_aliases: Vec::new(),
2164                pre_alias_comments: Vec::new(),
2165                trailing_comments: Vec::new(),
2166            })),
2167
2168            // Subquery: recursively process and add alias if inner returns a named column
2169            Expression::Subquery(sq) => {
2170                let processed = Self::add_column_aliases_to_query(Expression::Subquery(sq.clone()));
2171                // Subqueries that are already aliased keep their alias
2172                if sq.alias.is_some() {
2173                    processed
2174                } else {
2175                    // If there's no alias, keep it as-is (let TSQL handle it)
2176                    processed
2177                }
2178            }
2179
2180            // Star expressions (*) - don't alias
2181            Expression::Star(_) => expr,
2182
2183            // For other expressions, don't add an alias
2184            // (function calls, literals, etc. would need explicit aliases anyway)
2185            _ => expr,
2186        }
2187    }
2188
2189    /// Try to evaluate a constant arithmetic expression to a number literal.
2190    /// Returns the evaluated result if the expression is a constant arithmetic expression,
2191    /// otherwise returns the original expression.
2192    fn try_evaluate_constant(expr: &Expression) -> Option<i64> {
2193        match expr {
2194            Expression::Literal(Literal::Number(n)) => n.parse::<i64>().ok(),
2195            Expression::Add(op) => {
2196                let left = Self::try_evaluate_constant(&op.left)?;
2197                let right = Self::try_evaluate_constant(&op.right)?;
2198                Some(left + right)
2199            }
2200            Expression::Sub(op) => {
2201                let left = Self::try_evaluate_constant(&op.left)?;
2202                let right = Self::try_evaluate_constant(&op.right)?;
2203                Some(left - right)
2204            }
2205            Expression::Mul(op) => {
2206                let left = Self::try_evaluate_constant(&op.left)?;
2207                let right = Self::try_evaluate_constant(&op.right)?;
2208                Some(left * right)
2209            }
2210            Expression::Div(op) => {
2211                let left = Self::try_evaluate_constant(&op.left)?;
2212                let right = Self::try_evaluate_constant(&op.right)?;
2213                if right != 0 {
2214                    Some(left / right)
2215                } else {
2216                    None
2217                }
2218            }
2219            Expression::Paren(p) => Self::try_evaluate_constant(&p.this),
2220            _ => None,
2221        }
2222    }
2223
2224    /// Check if an identifier is a reserved keyword for the current dialect
2225    fn is_reserved_keyword(&self, name: &str) -> bool {
2226        use crate::dialects::DialectType;
2227        let lower = name.to_lowercase();
2228        let lower_ref = lower.as_str();
2229
2230        match self.config.dialect {
2231            Some(DialectType::BigQuery) => reserved_keywords::BIGQUERY_RESERVED.contains(lower_ref),
2232            Some(DialectType::MySQL) | Some(DialectType::TiDB) => {
2233                reserved_keywords::MYSQL_RESERVED.contains(lower_ref)
2234            }
2235            Some(DialectType::Doris) => reserved_keywords::DORIS_RESERVED.contains(lower_ref),
2236            Some(DialectType::SingleStore) => {
2237                reserved_keywords::SINGLESTORE_RESERVED.contains(lower_ref)
2238            }
2239            Some(DialectType::StarRocks) => {
2240                reserved_keywords::STARROCKS_RESERVED.contains(lower_ref)
2241            }
2242            Some(DialectType::PostgreSQL)
2243            | Some(DialectType::CockroachDB)
2244            | Some(DialectType::Materialize)
2245            | Some(DialectType::RisingWave) => {
2246                reserved_keywords::POSTGRES_RESERVED.contains(lower_ref)
2247            }
2248            Some(DialectType::Redshift) => reserved_keywords::REDSHIFT_RESERVED.contains(lower_ref),
2249            // Snowflake: Python sqlglot has RESERVED_KEYWORDS = set() for Snowflake,
2250            // meaning it never quotes identifiers based on reserved word status.
2251            Some(DialectType::Snowflake) => false,
2252            // ClickHouse: don't quote reserved keywords to preserve identity output
2253            Some(DialectType::ClickHouse) => false,
2254            Some(DialectType::DuckDB) => reserved_keywords::DUCKDB_RESERVED.contains(lower_ref),
2255            // Teradata: Python sqlglot has RESERVED_KEYWORDS = set() for Teradata
2256            Some(DialectType::Teradata) => false,
2257            // TSQL, Fabric, Oracle, Spark, Hive, Solr: Python sqlglot has no RESERVED_KEYWORDS for these dialects, so don't quote identifiers
2258            Some(DialectType::TSQL)
2259            | Some(DialectType::Fabric)
2260            | Some(DialectType::Oracle)
2261            | Some(DialectType::Spark)
2262            | Some(DialectType::Databricks)
2263            | Some(DialectType::Hive)
2264            | Some(DialectType::Solr) => false,
2265            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
2266                reserved_keywords::PRESTO_TRINO_RESERVED.contains(lower_ref)
2267            }
2268            Some(DialectType::SQLite) => reserved_keywords::SQLITE_RESERVED.contains(lower_ref),
2269            // For Generic dialect or None, don't add extra quoting to preserve identity
2270            Some(DialectType::Generic) | None => false,
2271            // For other dialects, use standard SQL reserved keywords
2272            _ => reserved_keywords::SQL_RESERVED.contains(lower_ref),
2273        }
2274    }
2275
2276    /// Normalize function name based on dialect settings
2277    fn normalize_func_name(&self, name: &str) -> String {
2278        match self.config.normalize_functions {
2279            NormalizeFunctions::Upper => name.to_uppercase(),
2280            NormalizeFunctions::Lower => name.to_lowercase(),
2281            NormalizeFunctions::None => name.to_string(),
2282        }
2283    }
2284
2285    /// Generate a SQL string from an AST expression.
2286    ///
2287    /// This is the primary generation method. It clears any previous internal state,
2288    /// walks the expression tree, and returns the resulting SQL text. The output
2289    /// respects the [`GeneratorConfig`] that was supplied at construction time.
2290    ///
2291    /// The generator can be reused across multiple calls; each call to `generate`
2292    /// resets the internal buffer.
2293    pub fn generate(&mut self, expr: &Expression) -> Result<String> {
2294        self.output.clear();
2295        self.generate_expression(expr)?;
2296        Ok(std::mem::take(&mut self.output))
2297    }
2298
2299    /// Convenience: generate SQL with the default configuration (no dialect, compact output).
2300    ///
2301    /// This is a static helper that creates a throwaway `Generator` internally.
2302    /// For repeated generation, prefer constructing a `Generator` once and calling
2303    /// [`generate`](Self::generate) on it.
2304    pub fn sql(expr: &Expression) -> Result<String> {
2305        let mut gen = Generator::new();
2306        gen.generate(expr)
2307    }
2308
2309    /// Convenience: generate SQL with pretty-printing enabled (indented, multi-line).
2310    ///
2311    /// Produces human-readable output with newlines and indentation. A trailing
2312    /// semicolon is appended automatically if not already present.
2313    pub fn pretty_sql(expr: &Expression) -> Result<String> {
2314        let config = GeneratorConfig {
2315            pretty: true,
2316            ..Default::default()
2317        };
2318        let mut gen = Generator::with_config(config);
2319        let mut sql = gen.generate(expr)?;
2320        // Add semicolon for pretty output
2321        if !sql.ends_with(';') {
2322            sql.push(';');
2323        }
2324        Ok(sql)
2325    }
2326
2327    fn generate_expression(&mut self, expr: &Expression) -> Result<()> {
2328        match expr {
2329            Expression::Select(select) => self.generate_select(select),
2330            Expression::Union(union) => self.generate_union(union),
2331            Expression::Intersect(intersect) => self.generate_intersect(intersect),
2332            Expression::Except(except) => self.generate_except(except),
2333            Expression::Insert(insert) => self.generate_insert(insert),
2334            Expression::Update(update) => self.generate_update(update),
2335            Expression::Delete(delete) => self.generate_delete(delete),
2336            Expression::Literal(lit) => self.generate_literal(lit),
2337            Expression::Boolean(b) => self.generate_boolean(b),
2338            Expression::Null(_) => {
2339                self.write_keyword("NULL");
2340                Ok(())
2341            }
2342            Expression::Identifier(id) => self.generate_identifier(id),
2343            Expression::Column(col) => self.generate_column(col),
2344            Expression::Pseudocolumn(pc) => self.generate_pseudocolumn(pc),
2345            Expression::Connect(c) => self.generate_connect_expr(c),
2346            Expression::Prior(p) => self.generate_prior(p),
2347            Expression::ConnectByRoot(cbr) => self.generate_connect_by_root(cbr),
2348            Expression::MatchRecognize(mr) => self.generate_match_recognize(mr),
2349            Expression::Table(table) => self.generate_table(table),
2350            Expression::StageReference(sr) => self.generate_stage_reference(sr),
2351            Expression::HistoricalData(hd) => self.generate_historical_data(hd),
2352            Expression::JoinedTable(jt) => self.generate_joined_table(jt),
2353            Expression::Star(star) => self.generate_star(star),
2354            Expression::BracedWildcard(expr) => self.generate_braced_wildcard(expr),
2355            Expression::Alias(alias) => self.generate_alias(alias),
2356            Expression::Cast(cast) => self.generate_cast(cast),
2357            Expression::Collation(coll) => self.generate_collation(coll),
2358            Expression::Case(case) => self.generate_case(case),
2359            Expression::Function(func) => self.generate_function(func),
2360            Expression::AggregateFunction(func) => self.generate_aggregate_function(func),
2361            Expression::WindowFunction(wf) => self.generate_window_function(wf),
2362            Expression::WithinGroup(wg) => self.generate_within_group(wg),
2363            Expression::Interval(interval) => self.generate_interval(interval),
2364
2365            // String functions
2366            Expression::ConcatWs(f) => self.generate_concat_ws(f),
2367            Expression::Substring(f) => self.generate_substring(f),
2368            Expression::Upper(f) => self.generate_unary_func("UPPER", f),
2369            Expression::Lower(f) => self.generate_unary_func("LOWER", f),
2370            Expression::Length(f) => self.generate_unary_func("LENGTH", f),
2371            Expression::Trim(f) => self.generate_trim(f),
2372            Expression::LTrim(f) => self.generate_simple_func("LTRIM", &f.this),
2373            Expression::RTrim(f) => self.generate_simple_func("RTRIM", &f.this),
2374            Expression::Replace(f) => self.generate_replace(f),
2375            Expression::Reverse(f) => self.generate_simple_func("REVERSE", &f.this),
2376            Expression::Left(f) => self.generate_left_right("LEFT", f),
2377            Expression::Right(f) => self.generate_left_right("RIGHT", f),
2378            Expression::Repeat(f) => self.generate_repeat(f),
2379            Expression::Lpad(f) => self.generate_pad("LPAD", f),
2380            Expression::Rpad(f) => self.generate_pad("RPAD", f),
2381            Expression::Split(f) => self.generate_split(f),
2382            Expression::RegexpLike(f) => self.generate_regexp_like(f),
2383            Expression::RegexpReplace(f) => self.generate_regexp_replace(f),
2384            Expression::RegexpExtract(f) => self.generate_regexp_extract(f),
2385            Expression::Overlay(f) => self.generate_overlay(f),
2386
2387            // Math functions
2388            Expression::Abs(f) => self.generate_simple_func("ABS", &f.this),
2389            Expression::Round(f) => self.generate_round(f),
2390            Expression::Floor(f) => self.generate_floor(f),
2391            Expression::Ceil(f) => self.generate_ceil(f),
2392            Expression::Power(f) => self.generate_power(f),
2393            Expression::Sqrt(f) => self.generate_sqrt_cbrt(f, "SQRT", "|/"),
2394            Expression::Cbrt(f) => self.generate_sqrt_cbrt(f, "CBRT", "||/"),
2395            Expression::Ln(f) => self.generate_simple_func("LN", &f.this),
2396            Expression::Log(f) => self.generate_log(f),
2397            Expression::Exp(f) => self.generate_simple_func("EXP", &f.this),
2398            Expression::Sign(f) => self.generate_simple_func("SIGN", &f.this),
2399            Expression::Greatest(f) => self.generate_vararg_func("GREATEST", &f.expressions),
2400            Expression::Least(f) => self.generate_vararg_func("LEAST", &f.expressions),
2401
2402            // Date/time functions
2403            Expression::CurrentDate(_) => {
2404                self.write_keyword("CURRENT_DATE");
2405                Ok(())
2406            }
2407            Expression::CurrentTime(f) => self.generate_current_time(f),
2408            Expression::CurrentTimestamp(f) => self.generate_current_timestamp(f),
2409            Expression::AtTimeZone(f) => self.generate_at_time_zone(f),
2410            Expression::DateAdd(f) => self.generate_date_add(f, "DATE_ADD"),
2411            Expression::DateSub(f) => self.generate_date_add(f, "DATE_SUB"),
2412            Expression::DateDiff(f) => self.generate_datediff(f),
2413            Expression::DateTrunc(f) => self.generate_date_trunc(f),
2414            Expression::Extract(f) => self.generate_extract(f),
2415            Expression::ToDate(f) => self.generate_to_date(f),
2416            Expression::ToTimestamp(f) => self.generate_to_timestamp(f),
2417
2418            // Control flow functions
2419            Expression::Coalesce(f) => {
2420                // Use original function name if preserved (COALESCE, IFNULL)
2421                let func_name = f.original_name.as_deref().unwrap_or("COALESCE");
2422                self.generate_vararg_func(func_name, &f.expressions)
2423            }
2424            Expression::NullIf(f) => self.generate_binary_func("NULLIF", &f.this, &f.expression),
2425            Expression::IfFunc(f) => self.generate_if_func(f),
2426            Expression::IfNull(f) => self.generate_ifnull(f),
2427            Expression::Nvl(f) => self.generate_nvl(f),
2428            Expression::Nvl2(f) => self.generate_nvl2(f),
2429
2430            // Type conversion
2431            Expression::TryCast(cast) => self.generate_try_cast(cast),
2432            Expression::SafeCast(cast) => self.generate_safe_cast(cast),
2433
2434            // Typed aggregate functions
2435            Expression::Count(f) => self.generate_count(f),
2436            Expression::Sum(f) => self.generate_agg_func("SUM", f),
2437            Expression::Avg(f) => self.generate_agg_func("AVG", f),
2438            Expression::Min(f) => self.generate_agg_func("MIN", f),
2439            Expression::Max(f) => self.generate_agg_func("MAX", f),
2440            Expression::GroupConcat(f) => self.generate_group_concat(f),
2441            Expression::StringAgg(f) => self.generate_string_agg(f),
2442            Expression::ListAgg(f) => self.generate_listagg(f),
2443            Expression::ArrayAgg(f) => {
2444                // Allow cross-dialect transforms to override the function name
2445                // (e.g., COLLECT_LIST for Spark)
2446                let override_name = f
2447                    .name
2448                    .as_ref()
2449                    .filter(|n| n.to_uppercase() != "ARRAY_AGG")
2450                    .map(|n| n.to_uppercase());
2451                match override_name {
2452                    Some(name) => self.generate_agg_func(&name, f),
2453                    None => self.generate_agg_func("ARRAY_AGG", f),
2454                }
2455            }
2456            Expression::ArrayConcatAgg(f) => self.generate_agg_func("ARRAY_CONCAT_AGG", f),
2457            Expression::CountIf(f) => self.generate_agg_func("COUNT_IF", f),
2458            Expression::SumIf(f) => self.generate_sum_if(f),
2459            Expression::Stddev(f) => self.generate_agg_func("STDDEV", f),
2460            Expression::StddevPop(f) => self.generate_agg_func("STDDEV_POP", f),
2461            Expression::StddevSamp(f) => self.generate_stddev_samp(f),
2462            Expression::Variance(f) => self.generate_agg_func("VARIANCE", f),
2463            Expression::VarPop(f) => {
2464                let name = if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
2465                    "VARIANCE_POP"
2466                } else {
2467                    "VAR_POP"
2468                };
2469                self.generate_agg_func(name, f)
2470            }
2471            Expression::VarSamp(f) => self.generate_agg_func("VAR_SAMP", f),
2472            Expression::Skewness(f) => {
2473                let name = match self.config.dialect {
2474                    Some(DialectType::Snowflake) => "SKEW",
2475                    _ => "SKEWNESS",
2476                };
2477                self.generate_agg_func(name, f)
2478            }
2479            Expression::Median(f) => self.generate_agg_func("MEDIAN", f),
2480            Expression::Mode(f) => self.generate_agg_func("MODE", f),
2481            Expression::First(f) => self.generate_agg_func("FIRST", f),
2482            Expression::Last(f) => self.generate_agg_func("LAST", f),
2483            Expression::AnyValue(f) => self.generate_agg_func("ANY_VALUE", f),
2484            Expression::ApproxDistinct(f) => {
2485                match self.config.dialect {
2486                    Some(DialectType::Hive)
2487                    | Some(DialectType::Spark)
2488                    | Some(DialectType::Databricks)
2489                    | Some(DialectType::BigQuery) => {
2490                        // These dialects use APPROX_COUNT_DISTINCT (single arg only)
2491                        self.generate_agg_func("APPROX_COUNT_DISTINCT", f)
2492                    }
2493                    Some(DialectType::Redshift) => {
2494                        // Redshift uses APPROXIMATE COUNT(DISTINCT expr)
2495                        self.write_keyword("APPROXIMATE COUNT");
2496                        self.write("(");
2497                        self.write_keyword("DISTINCT");
2498                        self.write(" ");
2499                        self.generate_expression(&f.this)?;
2500                        self.write(")");
2501                        Ok(())
2502                    }
2503                    _ => self.generate_agg_func("APPROX_DISTINCT", f),
2504                }
2505            }
2506            Expression::ApproxCountDistinct(f) => {
2507                self.generate_agg_func("APPROX_COUNT_DISTINCT", f)
2508            }
2509            Expression::ApproxPercentile(f) => self.generate_approx_percentile(f),
2510            Expression::Percentile(f) => self.generate_percentile("PERCENTILE", f),
2511            Expression::LogicalAnd(f) => {
2512                let name = match self.config.dialect {
2513                    Some(DialectType::Snowflake) => "BOOLAND_AGG",
2514                    Some(DialectType::Spark)
2515                    | Some(DialectType::Databricks)
2516                    | Some(DialectType::PostgreSQL)
2517                    | Some(DialectType::DuckDB)
2518                    | Some(DialectType::Redshift) => "BOOL_AND",
2519                    Some(DialectType::Oracle)
2520                    | Some(DialectType::SQLite)
2521                    | Some(DialectType::MySQL) => "MIN",
2522                    _ => "BOOL_AND",
2523                };
2524                self.generate_agg_func(name, f)
2525            }
2526            Expression::LogicalOr(f) => {
2527                let name = match self.config.dialect {
2528                    Some(DialectType::Snowflake) => "BOOLOR_AGG",
2529                    Some(DialectType::Spark)
2530                    | Some(DialectType::Databricks)
2531                    | Some(DialectType::PostgreSQL)
2532                    | Some(DialectType::DuckDB)
2533                    | Some(DialectType::Redshift) => "BOOL_OR",
2534                    Some(DialectType::Oracle)
2535                    | Some(DialectType::SQLite)
2536                    | Some(DialectType::MySQL) => "MAX",
2537                    _ => "BOOL_OR",
2538                };
2539                self.generate_agg_func(name, f)
2540            }
2541
2542            // Typed window functions
2543            Expression::RowNumber(_) => {
2544                if self.config.dialect == Some(DialectType::ClickHouse) {
2545                    self.write("row_number");
2546                } else {
2547                    self.write_keyword("ROW_NUMBER");
2548                }
2549                self.write("()");
2550                Ok(())
2551            }
2552            Expression::Rank(r) => {
2553                self.write_keyword("RANK");
2554                self.write("(");
2555                // Oracle hypothetical rank args: RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2556                if !r.args.is_empty() {
2557                    for (i, arg) in r.args.iter().enumerate() {
2558                        if i > 0 {
2559                            self.write(", ");
2560                        }
2561                        self.generate_expression(arg)?;
2562                    }
2563                } else if let Some(order_by) = &r.order_by {
2564                    // DuckDB: RANK(ORDER BY col)
2565                    self.write_keyword(" ORDER BY ");
2566                    for (i, ob) in order_by.iter().enumerate() {
2567                        if i > 0 {
2568                            self.write(", ");
2569                        }
2570                        self.generate_ordered(ob)?;
2571                    }
2572                }
2573                self.write(")");
2574                Ok(())
2575            }
2576            Expression::DenseRank(dr) => {
2577                self.write_keyword("DENSE_RANK");
2578                self.write("(");
2579                // Oracle hypothetical rank args: DENSE_RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2580                for (i, arg) in dr.args.iter().enumerate() {
2581                    if i > 0 {
2582                        self.write(", ");
2583                    }
2584                    self.generate_expression(arg)?;
2585                }
2586                self.write(")");
2587                Ok(())
2588            }
2589            Expression::NTile(f) => self.generate_ntile(f),
2590            Expression::Lead(f) => self.generate_lead_lag("LEAD", f),
2591            Expression::Lag(f) => self.generate_lead_lag("LAG", f),
2592            Expression::FirstValue(f) => self.generate_value_func("FIRST_VALUE", f),
2593            Expression::LastValue(f) => self.generate_value_func("LAST_VALUE", f),
2594            Expression::NthValue(f) => self.generate_nth_value(f),
2595            Expression::PercentRank(pr) => {
2596                self.write_keyword("PERCENT_RANK");
2597                self.write("(");
2598                // Oracle hypothetical rank args: PERCENT_RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2599                if !pr.args.is_empty() {
2600                    for (i, arg) in pr.args.iter().enumerate() {
2601                        if i > 0 {
2602                            self.write(", ");
2603                        }
2604                        self.generate_expression(arg)?;
2605                    }
2606                } else if let Some(order_by) = &pr.order_by {
2607                    // DuckDB: PERCENT_RANK(ORDER BY col)
2608                    self.write_keyword(" ORDER BY ");
2609                    for (i, ob) in order_by.iter().enumerate() {
2610                        if i > 0 {
2611                            self.write(", ");
2612                        }
2613                        self.generate_ordered(ob)?;
2614                    }
2615                }
2616                self.write(")");
2617                Ok(())
2618            }
2619            Expression::CumeDist(cd) => {
2620                self.write_keyword("CUME_DIST");
2621                self.write("(");
2622                // Oracle hypothetical rank args: CUME_DIST(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2623                if !cd.args.is_empty() {
2624                    for (i, arg) in cd.args.iter().enumerate() {
2625                        if i > 0 {
2626                            self.write(", ");
2627                        }
2628                        self.generate_expression(arg)?;
2629                    }
2630                } else if let Some(order_by) = &cd.order_by {
2631                    // DuckDB: CUME_DIST(ORDER BY col)
2632                    self.write_keyword(" ORDER BY ");
2633                    for (i, ob) in order_by.iter().enumerate() {
2634                        if i > 0 {
2635                            self.write(", ");
2636                        }
2637                        self.generate_ordered(ob)?;
2638                    }
2639                }
2640                self.write(")");
2641                Ok(())
2642            }
2643            Expression::PercentileCont(f) => self.generate_percentile("PERCENTILE_CONT", f),
2644            Expression::PercentileDisc(f) => self.generate_percentile("PERCENTILE_DISC", f),
2645
2646            // Additional string functions
2647            Expression::Contains(f) => {
2648                self.generate_binary_func("CONTAINS", &f.this, &f.expression)
2649            }
2650            Expression::StartsWith(f) => {
2651                let name = match self.config.dialect {
2652                    Some(DialectType::Spark) | Some(DialectType::Databricks) => "STARTSWITH",
2653                    _ => "STARTS_WITH",
2654                };
2655                self.generate_binary_func(name, &f.this, &f.expression)
2656            }
2657            Expression::EndsWith(f) => {
2658                let name = match self.config.dialect {
2659                    Some(DialectType::Snowflake) => "ENDSWITH",
2660                    Some(DialectType::Spark) | Some(DialectType::Databricks) => "ENDSWITH",
2661                    Some(DialectType::ClickHouse) => "endsWith",
2662                    _ => "ENDS_WITH",
2663                };
2664                self.generate_binary_func(name, &f.this, &f.expression)
2665            }
2666            Expression::Position(f) => self.generate_position(f),
2667            Expression::Initcap(f) => match self.config.dialect {
2668                Some(DialectType::Presto)
2669                | Some(DialectType::Trino)
2670                | Some(DialectType::Athena) => {
2671                    self.write_keyword("REGEXP_REPLACE");
2672                    self.write("(");
2673                    self.generate_expression(&f.this)?;
2674                    self.write(", '(\\w)(\\w*)', x -> UPPER(x[1]) || LOWER(x[2]))");
2675                    Ok(())
2676                }
2677                _ => self.generate_simple_func("INITCAP", &f.this),
2678            },
2679            Expression::Ascii(f) => self.generate_simple_func("ASCII", &f.this),
2680            Expression::Chr(f) => self.generate_simple_func("CHR", &f.this),
2681            Expression::CharFunc(f) => self.generate_char_func(f),
2682            Expression::Soundex(f) => self.generate_simple_func("SOUNDEX", &f.this),
2683            Expression::Levenshtein(f) => {
2684                self.generate_binary_func("LEVENSHTEIN", &f.this, &f.expression)
2685            }
2686
2687            // Additional math functions
2688            Expression::ModFunc(f) => self.generate_mod_func(f),
2689            Expression::Random(_) => {
2690                self.write_keyword("RANDOM");
2691                self.write("()");
2692                Ok(())
2693            }
2694            Expression::Rand(f) => self.generate_rand(f),
2695            Expression::TruncFunc(f) => self.generate_truncate_func(f),
2696            Expression::Pi(_) => {
2697                self.write_keyword("PI");
2698                self.write("()");
2699                Ok(())
2700            }
2701            Expression::Radians(f) => self.generate_simple_func("RADIANS", &f.this),
2702            Expression::Degrees(f) => self.generate_simple_func("DEGREES", &f.this),
2703            Expression::Sin(f) => self.generate_simple_func("SIN", &f.this),
2704            Expression::Cos(f) => self.generate_simple_func("COS", &f.this),
2705            Expression::Tan(f) => self.generate_simple_func("TAN", &f.this),
2706            Expression::Asin(f) => self.generate_simple_func("ASIN", &f.this),
2707            Expression::Acos(f) => self.generate_simple_func("ACOS", &f.this),
2708            Expression::Atan(f) => self.generate_simple_func("ATAN", &f.this),
2709            Expression::Atan2(f) => {
2710                let name = f.original_name.as_deref().unwrap_or("ATAN2");
2711                self.generate_binary_func(name, &f.this, &f.expression)
2712            }
2713
2714            // Control flow
2715            Expression::Decode(f) => self.generate_decode(f),
2716
2717            // Additional date/time functions
2718            Expression::DateFormat(f) => self.generate_date_format("DATE_FORMAT", f),
2719            Expression::FormatDate(f) => self.generate_date_format("FORMAT_DATE", f),
2720            Expression::Year(f) => self.generate_simple_func("YEAR", &f.this),
2721            Expression::Month(f) => self.generate_simple_func("MONTH", &f.this),
2722            Expression::Day(f) => self.generate_simple_func("DAY", &f.this),
2723            Expression::Hour(f) => self.generate_simple_func("HOUR", &f.this),
2724            Expression::Minute(f) => self.generate_simple_func("MINUTE", &f.this),
2725            Expression::Second(f) => self.generate_simple_func("SECOND", &f.this),
2726            Expression::DayOfWeek(f) => {
2727                let name = match self.config.dialect {
2728                    Some(DialectType::Presto)
2729                    | Some(DialectType::Trino)
2730                    | Some(DialectType::Athena) => "DAY_OF_WEEK",
2731                    Some(DialectType::DuckDB) => "ISODOW",
2732                    _ => "DAYOFWEEK",
2733                };
2734                self.generate_simple_func(name, &f.this)
2735            }
2736            Expression::DayOfMonth(f) => {
2737                let name = match self.config.dialect {
2738                    Some(DialectType::Presto)
2739                    | Some(DialectType::Trino)
2740                    | Some(DialectType::Athena) => "DAY_OF_MONTH",
2741                    _ => "DAYOFMONTH",
2742                };
2743                self.generate_simple_func(name, &f.this)
2744            }
2745            Expression::DayOfYear(f) => {
2746                let name = match self.config.dialect {
2747                    Some(DialectType::Presto)
2748                    | Some(DialectType::Trino)
2749                    | Some(DialectType::Athena) => "DAY_OF_YEAR",
2750                    _ => "DAYOFYEAR",
2751                };
2752                self.generate_simple_func(name, &f.this)
2753            }
2754            Expression::WeekOfYear(f) => {
2755                // Python sqlglot default is WEEK_OF_YEAR; Hive/DuckDB/Spark/MySQL override to WEEKOFYEAR
2756                let name = match self.config.dialect {
2757                    Some(DialectType::Hive)
2758                    | Some(DialectType::DuckDB)
2759                    | Some(DialectType::Spark)
2760                    | Some(DialectType::Databricks)
2761                    | Some(DialectType::MySQL) => "WEEKOFYEAR",
2762                    _ => "WEEK_OF_YEAR",
2763                };
2764                self.generate_simple_func(name, &f.this)
2765            }
2766            Expression::Quarter(f) => self.generate_simple_func("QUARTER", &f.this),
2767            Expression::AddMonths(f) => {
2768                self.generate_binary_func("ADD_MONTHS", &f.this, &f.expression)
2769            }
2770            Expression::MonthsBetween(f) => {
2771                self.generate_binary_func("MONTHS_BETWEEN", &f.this, &f.expression)
2772            }
2773            Expression::LastDay(f) => self.generate_last_day(f),
2774            Expression::NextDay(f) => self.generate_binary_func("NEXT_DAY", &f.this, &f.expression),
2775            Expression::Epoch(f) => self.generate_simple_func("EPOCH", &f.this),
2776            Expression::EpochMs(f) => self.generate_simple_func("EPOCH_MS", &f.this),
2777            Expression::FromUnixtime(f) => self.generate_from_unixtime(f),
2778            Expression::UnixTimestamp(f) => self.generate_unix_timestamp(f),
2779            Expression::MakeDate(f) => self.generate_make_date(f),
2780            Expression::MakeTimestamp(f) => self.generate_make_timestamp(f),
2781            Expression::TimestampTrunc(f) => self.generate_date_trunc(f),
2782
2783            // Array functions
2784            Expression::ArrayFunc(f) => self.generate_array_constructor(f),
2785            Expression::ArrayLength(f) => self.generate_simple_func("ARRAY_LENGTH", &f.this),
2786            Expression::ArraySize(f) => self.generate_simple_func("ARRAY_SIZE", &f.this),
2787            Expression::Cardinality(f) => self.generate_simple_func("CARDINALITY", &f.this),
2788            Expression::ArrayContains(f) => {
2789                self.generate_binary_func("ARRAY_CONTAINS", &f.this, &f.expression)
2790            }
2791            Expression::ArrayPosition(f) => {
2792                self.generate_binary_func("ARRAY_POSITION", &f.this, &f.expression)
2793            }
2794            Expression::ArrayAppend(f) => {
2795                self.generate_binary_func("ARRAY_APPEND", &f.this, &f.expression)
2796            }
2797            Expression::ArrayPrepend(f) => {
2798                self.generate_binary_func("ARRAY_PREPEND", &f.this, &f.expression)
2799            }
2800            Expression::ArrayConcat(f) => self.generate_vararg_func("ARRAY_CONCAT", &f.expressions),
2801            Expression::ArraySort(f) => self.generate_array_sort(f),
2802            Expression::ArrayReverse(f) => self.generate_simple_func("ARRAY_REVERSE", &f.this),
2803            Expression::ArrayDistinct(f) => self.generate_simple_func("ARRAY_DISTINCT", &f.this),
2804            Expression::ArrayJoin(f) => self.generate_array_join("ARRAY_JOIN", f),
2805            Expression::ArrayToString(f) => self.generate_array_join("ARRAY_TO_STRING", f),
2806            Expression::Unnest(f) => self.generate_unnest(f),
2807            Expression::Explode(f) => self.generate_simple_func("EXPLODE", &f.this),
2808            Expression::ExplodeOuter(f) => self.generate_simple_func("EXPLODE_OUTER", &f.this),
2809            Expression::ArrayFilter(f) => self.generate_array_filter(f),
2810            Expression::ArrayTransform(f) => self.generate_array_transform(f),
2811            Expression::ArrayFlatten(f) => self.generate_simple_func("FLATTEN", &f.this),
2812            Expression::ArrayCompact(f) => {
2813                if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
2814                    // DuckDB: ARRAY_COMPACT(arr) -> LIST_FILTER(arr, _u -> NOT _u IS NULL)
2815                    self.write("LIST_FILTER(");
2816                    self.generate_expression(&f.this)?;
2817                    self.write(", _u -> NOT _u IS NULL)");
2818                    Ok(())
2819                } else {
2820                    self.generate_simple_func("ARRAY_COMPACT", &f.this)
2821                }
2822            }
2823            Expression::ArrayIntersect(f) => {
2824                let func_name = f.original_name.as_deref().unwrap_or("ARRAY_INTERSECT");
2825                self.generate_vararg_func(func_name, &f.expressions)
2826            }
2827            Expression::ArrayUnion(f) => {
2828                self.generate_binary_func("ARRAY_UNION", &f.this, &f.expression)
2829            }
2830            Expression::ArrayExcept(f) => {
2831                self.generate_binary_func("ARRAY_EXCEPT", &f.this, &f.expression)
2832            }
2833            Expression::ArrayRemove(f) => {
2834                self.generate_binary_func("ARRAY_REMOVE", &f.this, &f.expression)
2835            }
2836            Expression::ArrayZip(f) => self.generate_vararg_func("ARRAYS_ZIP", &f.expressions),
2837            Expression::Sequence(f) => self.generate_sequence("SEQUENCE", f),
2838            Expression::Generate(f) => self.generate_sequence("GENERATE_SERIES", f),
2839
2840            // Struct functions
2841            Expression::StructFunc(f) => self.generate_struct_constructor(f),
2842            Expression::StructExtract(f) => self.generate_struct_extract(f),
2843            Expression::NamedStruct(f) => self.generate_named_struct(f),
2844
2845            // Map functions
2846            Expression::MapFunc(f) => self.generate_map_constructor(f),
2847            Expression::MapFromEntries(f) => self.generate_simple_func("MAP_FROM_ENTRIES", &f.this),
2848            Expression::MapFromArrays(f) => {
2849                self.generate_binary_func("MAP_FROM_ARRAYS", &f.this, &f.expression)
2850            }
2851            Expression::MapKeys(f) => self.generate_simple_func("MAP_KEYS", &f.this),
2852            Expression::MapValues(f) => self.generate_simple_func("MAP_VALUES", &f.this),
2853            Expression::MapContainsKey(f) => {
2854                self.generate_binary_func("MAP_CONTAINS_KEY", &f.this, &f.expression)
2855            }
2856            Expression::MapConcat(f) => self.generate_vararg_func("MAP_CONCAT", &f.expressions),
2857            Expression::ElementAt(f) => {
2858                self.generate_binary_func("ELEMENT_AT", &f.this, &f.expression)
2859            }
2860            Expression::TransformKeys(f) => self.generate_transform_func("TRANSFORM_KEYS", f),
2861            Expression::TransformValues(f) => self.generate_transform_func("TRANSFORM_VALUES", f),
2862
2863            // JSON functions
2864            Expression::JsonExtract(f) => self.generate_json_extract("JSON_EXTRACT", f),
2865            Expression::JsonExtractScalar(f) => {
2866                self.generate_json_extract("JSON_EXTRACT_SCALAR", f)
2867            }
2868            Expression::JsonExtractPath(f) => self.generate_json_path("JSON_EXTRACT_PATH", f),
2869            Expression::JsonArray(f) => self.generate_vararg_func("JSON_ARRAY", &f.expressions),
2870            Expression::JsonObject(f) => self.generate_json_object(f),
2871            Expression::JsonQuery(f) => self.generate_json_extract("JSON_QUERY", f),
2872            Expression::JsonValue(f) => self.generate_json_extract("JSON_VALUE", f),
2873            Expression::JsonArrayLength(f) => {
2874                self.generate_simple_func("JSON_ARRAY_LENGTH", &f.this)
2875            }
2876            Expression::JsonKeys(f) => self.generate_simple_func("JSON_KEYS", &f.this),
2877            Expression::JsonType(f) => self.generate_simple_func("JSON_TYPE", &f.this),
2878            Expression::ParseJson(f) => {
2879                let name = match self.config.dialect {
2880                    Some(DialectType::Presto)
2881                    | Some(DialectType::Trino)
2882                    | Some(DialectType::Athena) => "JSON_PARSE",
2883                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
2884                        // PostgreSQL: CAST(x AS JSON)
2885                        self.write_keyword("CAST");
2886                        self.write("(");
2887                        self.generate_expression(&f.this)?;
2888                        self.write_keyword(" AS ");
2889                        self.write_keyword("JSON");
2890                        self.write(")");
2891                        return Ok(());
2892                    }
2893                    Some(DialectType::Hive)
2894                    | Some(DialectType::Spark)
2895                    | Some(DialectType::MySQL)
2896                    | Some(DialectType::SingleStore)
2897                    | Some(DialectType::TiDB)
2898                    | Some(DialectType::TSQL) => {
2899                        // Hive/Spark/MySQL/TSQL: just emit the string literal
2900                        self.generate_expression(&f.this)?;
2901                        return Ok(());
2902                    }
2903                    Some(DialectType::DuckDB) => "JSON",
2904                    _ => "PARSE_JSON",
2905                };
2906                self.generate_simple_func(name, &f.this)
2907            }
2908            Expression::ToJson(f) => self.generate_simple_func("TO_JSON", &f.this),
2909            Expression::JsonSet(f) => self.generate_json_modify("JSON_SET", f),
2910            Expression::JsonInsert(f) => self.generate_json_modify("JSON_INSERT", f),
2911            Expression::JsonRemove(f) => self.generate_json_path("JSON_REMOVE", f),
2912            Expression::JsonMergePatch(f) => {
2913                self.generate_binary_func("JSON_MERGE_PATCH", &f.this, &f.expression)
2914            }
2915            Expression::JsonArrayAgg(f) => self.generate_json_array_agg(f),
2916            Expression::JsonObjectAgg(f) => self.generate_json_object_agg(f),
2917
2918            // Type casting/conversion
2919            Expression::Convert(f) => self.generate_convert(f),
2920            Expression::Typeof(f) => self.generate_simple_func("TYPEOF", &f.this),
2921
2922            // Additional expressions
2923            Expression::Lambda(f) => self.generate_lambda(f),
2924            Expression::Parameter(f) => self.generate_parameter(f),
2925            Expression::Placeholder(f) => self.generate_placeholder(f),
2926            Expression::NamedArgument(f) => self.generate_named_argument(f),
2927            Expression::TableArgument(f) => self.generate_table_argument(f),
2928            Expression::SqlComment(f) => self.generate_sql_comment(f),
2929
2930            // Additional predicates
2931            Expression::NullSafeEq(op) => self.generate_null_safe_eq(op),
2932            Expression::NullSafeNeq(op) => self.generate_null_safe_neq(op),
2933            Expression::Glob(op) => self.generate_binary_op(op, "GLOB"),
2934            Expression::SimilarTo(f) => self.generate_similar_to(f),
2935            Expression::Any(f) => self.generate_quantified("ANY", f),
2936            Expression::All(f) => self.generate_quantified("ALL", f),
2937            Expression::Overlaps(f) => self.generate_overlaps(f),
2938
2939            // Bitwise operations
2940            Expression::BitwiseLeftShift(op) => {
2941                if matches!(
2942                    self.config.dialect,
2943                    Some(DialectType::Presto) | Some(DialectType::Trino)
2944                ) {
2945                    self.write_keyword("BITWISE_ARITHMETIC_SHIFT_LEFT");
2946                    self.write("(");
2947                    self.generate_expression(&op.left)?;
2948                    self.write(", ");
2949                    self.generate_expression(&op.right)?;
2950                    self.write(")");
2951                    Ok(())
2952                } else if matches!(
2953                    self.config.dialect,
2954                    Some(DialectType::Spark) | Some(DialectType::Databricks)
2955                ) {
2956                    self.write_keyword("SHIFTLEFT");
2957                    self.write("(");
2958                    self.generate_expression(&op.left)?;
2959                    self.write(", ");
2960                    self.generate_expression(&op.right)?;
2961                    self.write(")");
2962                    Ok(())
2963                } else {
2964                    self.generate_binary_op(op, "<<")
2965                }
2966            }
2967            Expression::BitwiseRightShift(op) => {
2968                if matches!(
2969                    self.config.dialect,
2970                    Some(DialectType::Presto) | Some(DialectType::Trino)
2971                ) {
2972                    self.write_keyword("BITWISE_ARITHMETIC_SHIFT_RIGHT");
2973                    self.write("(");
2974                    self.generate_expression(&op.left)?;
2975                    self.write(", ");
2976                    self.generate_expression(&op.right)?;
2977                    self.write(")");
2978                    Ok(())
2979                } else if matches!(
2980                    self.config.dialect,
2981                    Some(DialectType::Spark) | Some(DialectType::Databricks)
2982                ) {
2983                    self.write_keyword("SHIFTRIGHT");
2984                    self.write("(");
2985                    self.generate_expression(&op.left)?;
2986                    self.write(", ");
2987                    self.generate_expression(&op.right)?;
2988                    self.write(")");
2989                    Ok(())
2990                } else {
2991                    self.generate_binary_op(op, ">>")
2992                }
2993            }
2994            Expression::BitwiseAndAgg(f) => self.generate_agg_func("BIT_AND", f),
2995            Expression::BitwiseOrAgg(f) => self.generate_agg_func("BIT_OR", f),
2996            Expression::BitwiseXorAgg(f) => self.generate_agg_func("BIT_XOR", f),
2997
2998            // Array/struct/map access
2999            Expression::Subscript(s) => self.generate_subscript(s),
3000            Expression::Dot(d) => self.generate_dot_access(d),
3001            Expression::MethodCall(m) => self.generate_method_call(m),
3002            Expression::ArraySlice(s) => self.generate_array_slice(s),
3003
3004            Expression::And(op) => self.generate_binary_op(op, "AND"),
3005            Expression::Or(op) => self.generate_binary_op(op, "OR"),
3006            Expression::Add(op) => self.generate_binary_op(op, "+"),
3007            Expression::Sub(op) => self.generate_binary_op(op, "-"),
3008            Expression::Mul(op) => self.generate_binary_op(op, "*"),
3009            Expression::Div(op) => self.generate_binary_op(op, "/"),
3010            Expression::IntDiv(f) => {
3011                use crate::dialects::DialectType;
3012                if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
3013                    // DuckDB uses // operator for integer division
3014                    self.generate_expression(&f.this)?;
3015                    self.write(" // ");
3016                    self.generate_expression(&f.expression)?;
3017                    Ok(())
3018                } else if matches!(
3019                    self.config.dialect,
3020                    Some(DialectType::Hive | DialectType::Spark | DialectType::Databricks)
3021                ) {
3022                    // Hive/Spark use DIV as an infix operator
3023                    self.generate_expression(&f.this)?;
3024                    self.write(" ");
3025                    self.write_keyword("DIV");
3026                    self.write(" ");
3027                    self.generate_expression(&f.expression)?;
3028                    Ok(())
3029                } else {
3030                    // Other dialects use DIV function
3031                    self.write_keyword("DIV");
3032                    self.write("(");
3033                    self.generate_expression(&f.this)?;
3034                    self.write(", ");
3035                    self.generate_expression(&f.expression)?;
3036                    self.write(")");
3037                    Ok(())
3038                }
3039            }
3040            Expression::Mod(op) => {
3041                if matches!(self.config.dialect, Some(DialectType::Teradata)) {
3042                    self.generate_binary_op(op, "MOD")
3043                } else {
3044                    self.generate_binary_op(op, "%")
3045                }
3046            }
3047            Expression::Eq(op) => self.generate_binary_op(op, "="),
3048            Expression::Neq(op) => self.generate_binary_op(op, "<>"),
3049            Expression::Lt(op) => self.generate_binary_op(op, "<"),
3050            Expression::Lte(op) => self.generate_binary_op(op, "<="),
3051            Expression::Gt(op) => self.generate_binary_op(op, ">"),
3052            Expression::Gte(op) => self.generate_binary_op(op, ">="),
3053            Expression::Like(op) => self.generate_like_op(op, "LIKE"),
3054            Expression::ILike(op) => self.generate_like_op(op, "ILIKE"),
3055            Expression::Match(op) => self.generate_binary_op(op, "MATCH"),
3056            Expression::Concat(op) => {
3057                // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
3058                if self.config.dialect == Some(DialectType::Solr) {
3059                    self.generate_binary_op(op, "OR")
3060                } else {
3061                    self.generate_binary_op(op, "||")
3062                }
3063            }
3064            Expression::BitwiseAnd(op) => {
3065                // Presto/Trino use BITWISE_AND function
3066                if matches!(
3067                    self.config.dialect,
3068                    Some(DialectType::Presto) | Some(DialectType::Trino)
3069                ) {
3070                    self.write_keyword("BITWISE_AND");
3071                    self.write("(");
3072                    self.generate_expression(&op.left)?;
3073                    self.write(", ");
3074                    self.generate_expression(&op.right)?;
3075                    self.write(")");
3076                    Ok(())
3077                } else {
3078                    self.generate_binary_op(op, "&")
3079                }
3080            }
3081            Expression::BitwiseOr(op) => {
3082                // Presto/Trino use BITWISE_OR function
3083                if matches!(
3084                    self.config.dialect,
3085                    Some(DialectType::Presto) | Some(DialectType::Trino)
3086                ) {
3087                    self.write_keyword("BITWISE_OR");
3088                    self.write("(");
3089                    self.generate_expression(&op.left)?;
3090                    self.write(", ");
3091                    self.generate_expression(&op.right)?;
3092                    self.write(")");
3093                    Ok(())
3094                } else {
3095                    self.generate_binary_op(op, "|")
3096                }
3097            }
3098            Expression::BitwiseXor(op) => {
3099                // Presto/Trino use BITWISE_XOR function, PostgreSQL uses #, others use ^
3100                if matches!(
3101                    self.config.dialect,
3102                    Some(DialectType::Presto) | Some(DialectType::Trino)
3103                ) {
3104                    self.write_keyword("BITWISE_XOR");
3105                    self.write("(");
3106                    self.generate_expression(&op.left)?;
3107                    self.write(", ");
3108                    self.generate_expression(&op.right)?;
3109                    self.write(")");
3110                    Ok(())
3111                } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
3112                    self.generate_binary_op(op, "#")
3113                } else {
3114                    self.generate_binary_op(op, "^")
3115                }
3116            }
3117            Expression::Adjacent(op) => self.generate_binary_op(op, "-|-"),
3118            Expression::TsMatch(op) => self.generate_binary_op(op, "@@"),
3119            Expression::PropertyEQ(op) => self.generate_binary_op(op, ":="),
3120            Expression::ArrayContainsAll(op) => self.generate_binary_op(op, "@>"),
3121            Expression::ArrayContainedBy(op) => self.generate_binary_op(op, "<@"),
3122            Expression::ArrayOverlaps(op) => self.generate_binary_op(op, "&&"),
3123            Expression::JSONBContainsAllTopKeys(op) => self.generate_binary_op(op, "?&"),
3124            Expression::JSONBContainsAnyTopKeys(op) => self.generate_binary_op(op, "?|"),
3125            Expression::JSONBContains(f) => {
3126                // PostgreSQL JSONB contains key operator: a ? b
3127                self.generate_expression(&f.this)?;
3128                self.write_space();
3129                self.write("?");
3130                self.write_space();
3131                self.generate_expression(&f.expression)
3132            }
3133            Expression::JSONBDeleteAtPath(op) => self.generate_binary_op(op, "#-"),
3134            Expression::ExtendsLeft(op) => self.generate_binary_op(op, "&<"),
3135            Expression::ExtendsRight(op) => self.generate_binary_op(op, "&>"),
3136            Expression::Not(op) => self.generate_unary_op(op, "NOT"),
3137            Expression::Neg(op) => self.generate_unary_op(op, "-"),
3138            Expression::BitwiseNot(op) => {
3139                // Presto/Trino use BITWISE_NOT function
3140                if matches!(
3141                    self.config.dialect,
3142                    Some(DialectType::Presto) | Some(DialectType::Trino)
3143                ) {
3144                    self.write_keyword("BITWISE_NOT");
3145                    self.write("(");
3146                    self.generate_expression(&op.this)?;
3147                    self.write(")");
3148                    Ok(())
3149                } else {
3150                    self.generate_unary_op(op, "~")
3151                }
3152            }
3153            Expression::In(in_expr) => self.generate_in(in_expr),
3154            Expression::Between(between) => self.generate_between(between),
3155            Expression::IsNull(is_null) => self.generate_is_null(is_null),
3156            Expression::IsTrue(is_true) => self.generate_is_true(is_true),
3157            Expression::IsFalse(is_false) => self.generate_is_false(is_false),
3158            Expression::IsJson(is_json) => self.generate_is_json(is_json),
3159            Expression::Is(is_expr) => self.generate_is(is_expr),
3160            Expression::Exists(exists) => self.generate_exists(exists),
3161            Expression::MemberOf(member_of) => self.generate_member_of(member_of),
3162            Expression::Subquery(subquery) => self.generate_subquery(subquery),
3163            Expression::Paren(paren) => {
3164                // JoinedTable already outputs its own parentheses, so don't double-wrap
3165                let skip_parens = matches!(&paren.this, Expression::JoinedTable(_));
3166
3167                if !skip_parens {
3168                    self.write("(");
3169                    if self.config.pretty {
3170                        self.write_newline();
3171                        self.indent_level += 1;
3172                        self.write_indent();
3173                    }
3174                }
3175                self.generate_expression(&paren.this)?;
3176                if !skip_parens {
3177                    if self.config.pretty {
3178                        self.write_newline();
3179                        self.indent_level -= 1;
3180                        self.write_indent();
3181                    }
3182                    self.write(")");
3183                }
3184                // Output trailing comments after closing paren
3185                for comment in &paren.trailing_comments {
3186                    self.write(" ");
3187                    self.write_formatted_comment(comment);
3188                }
3189                Ok(())
3190            }
3191            Expression::Array(arr) => self.generate_array(arr),
3192            Expression::Tuple(tuple) => self.generate_tuple(tuple),
3193            Expression::PipeOperator(pipe) => self.generate_pipe_operator(pipe),
3194            Expression::Ordered(ordered) => self.generate_ordered(ordered),
3195            Expression::DataType(dt) => self.generate_data_type(dt),
3196            Expression::Raw(raw) => {
3197                self.write(&raw.sql);
3198                Ok(())
3199            }
3200            Expression::Command(cmd) => {
3201                self.write(&cmd.this);
3202                Ok(())
3203            }
3204            Expression::Kill(kill) => {
3205                self.write_keyword("KILL");
3206                if let Some(kind) = &kill.kind {
3207                    self.write_space();
3208                    self.write_keyword(kind);
3209                }
3210                self.write_space();
3211                self.generate_expression(&kill.this)?;
3212                Ok(())
3213            }
3214            Expression::Execute(exec) => {
3215                self.write_keyword("EXEC");
3216                self.write_space();
3217                self.generate_expression(&exec.this)?;
3218                for (i, param) in exec.parameters.iter().enumerate() {
3219                    if i == 0 {
3220                        self.write_space();
3221                    } else {
3222                        self.write(", ");
3223                    }
3224                    self.write(&param.name);
3225                    self.write("=");
3226                    self.generate_expression(&param.value)?;
3227                }
3228                Ok(())
3229            }
3230            Expression::Annotated(annotated) => {
3231                self.generate_expression(&annotated.this)?;
3232                for comment in &annotated.trailing_comments {
3233                    self.write(" ");
3234                    self.write_formatted_comment(comment);
3235                }
3236                Ok(())
3237            }
3238
3239            // DDL statements
3240            Expression::CreateTable(ct) => self.generate_create_table(ct),
3241            Expression::DropTable(dt) => self.generate_drop_table(dt),
3242            Expression::AlterTable(at) => self.generate_alter_table(at),
3243            Expression::CreateIndex(ci) => self.generate_create_index(ci),
3244            Expression::DropIndex(di) => self.generate_drop_index(di),
3245            Expression::CreateView(cv) => self.generate_create_view(cv),
3246            Expression::DropView(dv) => self.generate_drop_view(dv),
3247            Expression::AlterView(av) => self.generate_alter_view(av),
3248            Expression::AlterIndex(ai) => self.generate_alter_index(ai),
3249            Expression::Truncate(tr) => self.generate_truncate(tr),
3250            Expression::Use(u) => self.generate_use(u),
3251            // Phase 4: Additional DDL statements
3252            Expression::CreateSchema(cs) => self.generate_create_schema(cs),
3253            Expression::DropSchema(ds) => self.generate_drop_schema(ds),
3254            Expression::DropNamespace(dn) => self.generate_drop_namespace(dn),
3255            Expression::CreateDatabase(cd) => self.generate_create_database(cd),
3256            Expression::DropDatabase(dd) => self.generate_drop_database(dd),
3257            Expression::CreateFunction(cf) => self.generate_create_function(cf),
3258            Expression::DropFunction(df) => self.generate_drop_function(df),
3259            Expression::CreateProcedure(cp) => self.generate_create_procedure(cp),
3260            Expression::DropProcedure(dp) => self.generate_drop_procedure(dp),
3261            Expression::CreateSequence(cs) => self.generate_create_sequence(cs),
3262            Expression::DropSequence(ds) => self.generate_drop_sequence(ds),
3263            Expression::AlterSequence(als) => self.generate_alter_sequence(als),
3264            Expression::CreateTrigger(ct) => self.generate_create_trigger(ct),
3265            Expression::DropTrigger(dt) => self.generate_drop_trigger(dt),
3266            Expression::CreateType(ct) => self.generate_create_type(ct),
3267            Expression::DropType(dt) => self.generate_drop_type(dt),
3268            Expression::Describe(d) => self.generate_describe(d),
3269            Expression::Show(s) => self.generate_show(s),
3270
3271            // CACHE/UNCACHE/LOAD TABLE (Spark/Hive)
3272            Expression::Cache(c) => self.generate_cache(c),
3273            Expression::Uncache(u) => self.generate_uncache(u),
3274            Expression::LoadData(l) => self.generate_load_data(l),
3275            Expression::Pragma(p) => self.generate_pragma(p),
3276            Expression::Grant(g) => self.generate_grant(g),
3277            Expression::Revoke(r) => self.generate_revoke(r),
3278            Expression::Comment(c) => self.generate_comment(c),
3279            Expression::SetStatement(s) => self.generate_set_statement(s),
3280
3281            // PIVOT/UNPIVOT
3282            Expression::Pivot(pivot) => self.generate_pivot(pivot),
3283            Expression::Unpivot(unpivot) => self.generate_unpivot(unpivot),
3284
3285            // VALUES table constructor
3286            Expression::Values(values) => self.generate_values(values),
3287
3288            // === BATCH-GENERATED MATCH ARMS (481 variants) ===
3289            Expression::AIAgg(e) => self.generate_ai_agg(e),
3290            Expression::AIClassify(e) => self.generate_ai_classify(e),
3291            Expression::AddPartition(e) => self.generate_add_partition(e),
3292            Expression::AlgorithmProperty(e) => self.generate_algorithm_property(e),
3293            Expression::Aliases(e) => self.generate_aliases(e),
3294            Expression::AllowedValuesProperty(e) => self.generate_allowed_values_property(e),
3295            Expression::AlterColumn(e) => self.generate_alter_column(e),
3296            Expression::AlterSession(e) => self.generate_alter_session(e),
3297            Expression::AlterSet(e) => self.generate_alter_set(e),
3298            Expression::AlterSortKey(e) => self.generate_alter_sort_key(e),
3299            Expression::Analyze(e) => self.generate_analyze(e),
3300            Expression::AnalyzeDelete(e) => self.generate_analyze_delete(e),
3301            Expression::AnalyzeHistogram(e) => self.generate_analyze_histogram(e),
3302            Expression::AnalyzeListChainedRows(e) => self.generate_analyze_list_chained_rows(e),
3303            Expression::AnalyzeSample(e) => self.generate_analyze_sample(e),
3304            Expression::AnalyzeStatistics(e) => self.generate_analyze_statistics(e),
3305            Expression::AnalyzeValidate(e) => self.generate_analyze_validate(e),
3306            Expression::AnalyzeWith(e) => self.generate_analyze_with(e),
3307            Expression::Anonymous(e) => self.generate_anonymous(e),
3308            Expression::AnonymousAggFunc(e) => self.generate_anonymous_agg_func(e),
3309            Expression::Apply(e) => self.generate_apply(e),
3310            Expression::ApproxPercentileEstimate(e) => self.generate_approx_percentile_estimate(e),
3311            Expression::ApproxQuantile(e) => self.generate_approx_quantile(e),
3312            Expression::ApproxQuantiles(e) => self.generate_approx_quantiles(e),
3313            Expression::ApproxTopK(e) => self.generate_approx_top_k(e),
3314            Expression::ApproxTopKAccumulate(e) => self.generate_approx_top_k_accumulate(e),
3315            Expression::ApproxTopKCombine(e) => self.generate_approx_top_k_combine(e),
3316            Expression::ApproxTopKEstimate(e) => self.generate_approx_top_k_estimate(e),
3317            Expression::ApproxTopSum(e) => self.generate_approx_top_sum(e),
3318            Expression::ArgMax(e) => self.generate_arg_max(e),
3319            Expression::ArgMin(e) => self.generate_arg_min(e),
3320            Expression::ArrayAll(e) => self.generate_array_all(e),
3321            Expression::ArrayAny(e) => self.generate_array_any(e),
3322            Expression::ArrayConstructCompact(e) => self.generate_array_construct_compact(e),
3323            Expression::ArraySum(e) => self.generate_array_sum(e),
3324            Expression::AtIndex(e) => self.generate_at_index(e),
3325            Expression::Attach(e) => self.generate_attach(e),
3326            Expression::AttachOption(e) => self.generate_attach_option(e),
3327            Expression::AutoIncrementProperty(e) => self.generate_auto_increment_property(e),
3328            Expression::AutoRefreshProperty(e) => self.generate_auto_refresh_property(e),
3329            Expression::BackupProperty(e) => self.generate_backup_property(e),
3330            Expression::Base64DecodeBinary(e) => self.generate_base64_decode_binary(e),
3331            Expression::Base64DecodeString(e) => self.generate_base64_decode_string(e),
3332            Expression::Base64Encode(e) => self.generate_base64_encode(e),
3333            Expression::BlockCompressionProperty(e) => self.generate_block_compression_property(e),
3334            Expression::Booland(e) => self.generate_booland(e),
3335            Expression::Boolor(e) => self.generate_boolor(e),
3336            Expression::BuildProperty(e) => self.generate_build_property(e),
3337            Expression::ByteString(e) => self.generate_byte_string(e),
3338            Expression::CaseSpecificColumnConstraint(e) => {
3339                self.generate_case_specific_column_constraint(e)
3340            }
3341            Expression::CastToStrType(e) => self.generate_cast_to_str_type(e),
3342            Expression::Changes(e) => self.generate_changes(e),
3343            Expression::CharacterSetColumnConstraint(e) => {
3344                self.generate_character_set_column_constraint(e)
3345            }
3346            Expression::CharacterSetProperty(e) => self.generate_character_set_property(e),
3347            Expression::CheckColumnConstraint(e) => self.generate_check_column_constraint(e),
3348            Expression::CheckJson(e) => self.generate_check_json(e),
3349            Expression::CheckXml(e) => self.generate_check_xml(e),
3350            Expression::ChecksumProperty(e) => self.generate_checksum_property(e),
3351            Expression::Clone(e) => self.generate_clone(e),
3352            Expression::ClusterBy(e) => self.generate_cluster_by(e),
3353            Expression::ClusteredByProperty(e) => self.generate_clustered_by_property(e),
3354            Expression::CollateProperty(e) => self.generate_collate_property(e),
3355            Expression::ColumnConstraint(e) => self.generate_column_constraint(e),
3356            Expression::ColumnDef(e) => self.generate_column_def_expr(e),
3357            Expression::ColumnPosition(e) => self.generate_column_position(e),
3358            Expression::ColumnPrefix(e) => self.generate_column_prefix(e),
3359            Expression::Columns(e) => self.generate_columns(e),
3360            Expression::CombinedAggFunc(e) => self.generate_combined_agg_func(e),
3361            Expression::CombinedParameterizedAgg(e) => self.generate_combined_parameterized_agg(e),
3362            Expression::Commit(e) => self.generate_commit(e),
3363            Expression::Comprehension(e) => self.generate_comprehension(e),
3364            Expression::Compress(e) => self.generate_compress(e),
3365            Expression::CompressColumnConstraint(e) => self.generate_compress_column_constraint(e),
3366            Expression::ComputedColumnConstraint(e) => self.generate_computed_column_constraint(e),
3367            Expression::ConditionalInsert(e) => self.generate_conditional_insert(e),
3368            Expression::Constraint(e) => self.generate_constraint(e),
3369            Expression::ConvertTimezone(e) => self.generate_convert_timezone(e),
3370            Expression::ConvertToCharset(e) => self.generate_convert_to_charset(e),
3371            Expression::Copy(e) => self.generate_copy(e),
3372            Expression::CopyParameter(e) => self.generate_copy_parameter(e),
3373            Expression::Corr(e) => self.generate_corr(e),
3374            Expression::CosineDistance(e) => self.generate_cosine_distance(e),
3375            Expression::CovarPop(e) => self.generate_covar_pop(e),
3376            Expression::CovarSamp(e) => self.generate_covar_samp(e),
3377            Expression::Credentials(e) => self.generate_credentials(e),
3378            Expression::CredentialsProperty(e) => self.generate_credentials_property(e),
3379            Expression::Cte(e) => self.generate_cte(e),
3380            Expression::Cube(e) => self.generate_cube(e),
3381            Expression::CurrentDatetime(e) => self.generate_current_datetime(e),
3382            Expression::CurrentSchema(e) => self.generate_current_schema(e),
3383            Expression::CurrentSchemas(e) => self.generate_current_schemas(e),
3384            Expression::CurrentUser(e) => self.generate_current_user(e),
3385            Expression::DPipe(e) => self.generate_d_pipe(e),
3386            Expression::DataBlocksizeProperty(e) => self.generate_data_blocksize_property(e),
3387            Expression::DataDeletionProperty(e) => self.generate_data_deletion_property(e),
3388            Expression::Date(e) => self.generate_date_func(e),
3389            Expression::DateBin(e) => self.generate_date_bin(e),
3390            Expression::DateFormatColumnConstraint(e) => {
3391                self.generate_date_format_column_constraint(e)
3392            }
3393            Expression::DateFromParts(e) => self.generate_date_from_parts(e),
3394            Expression::Datetime(e) => self.generate_datetime(e),
3395            Expression::DatetimeAdd(e) => self.generate_datetime_add(e),
3396            Expression::DatetimeDiff(e) => self.generate_datetime_diff(e),
3397            Expression::DatetimeSub(e) => self.generate_datetime_sub(e),
3398            Expression::DatetimeTrunc(e) => self.generate_datetime_trunc(e),
3399            Expression::Dayname(e) => self.generate_dayname(e),
3400            Expression::Declare(e) => self.generate_declare(e),
3401            Expression::DeclareItem(e) => self.generate_declare_item(e),
3402            Expression::DecodeCase(e) => self.generate_decode_case(e),
3403            Expression::DecompressBinary(e) => self.generate_decompress_binary(e),
3404            Expression::DecompressString(e) => self.generate_decompress_string(e),
3405            Expression::Decrypt(e) => self.generate_decrypt(e),
3406            Expression::DecryptRaw(e) => self.generate_decrypt_raw(e),
3407            Expression::DefinerProperty(e) => self.generate_definer_property(e),
3408            Expression::Detach(e) => self.generate_detach(e),
3409            Expression::DictProperty(e) => self.generate_dict_property(e),
3410            Expression::DictRange(e) => self.generate_dict_range(e),
3411            Expression::Directory(e) => self.generate_directory(e),
3412            Expression::DistKeyProperty(e) => self.generate_dist_key_property(e),
3413            Expression::DistStyleProperty(e) => self.generate_dist_style_property(e),
3414            Expression::DistributeBy(e) => self.generate_distribute_by(e),
3415            Expression::DistributedByProperty(e) => self.generate_distributed_by_property(e),
3416            Expression::DotProduct(e) => self.generate_dot_product(e),
3417            Expression::DropPartition(e) => self.generate_drop_partition(e),
3418            Expression::DuplicateKeyProperty(e) => self.generate_duplicate_key_property(e),
3419            Expression::Elt(e) => self.generate_elt(e),
3420            Expression::Encode(e) => self.generate_encode(e),
3421            Expression::EncodeProperty(e) => self.generate_encode_property(e),
3422            Expression::Encrypt(e) => self.generate_encrypt(e),
3423            Expression::EncryptRaw(e) => self.generate_encrypt_raw(e),
3424            Expression::EngineProperty(e) => self.generate_engine_property(e),
3425            Expression::EnviromentProperty(e) => self.generate_enviroment_property(e),
3426            Expression::EphemeralColumnConstraint(e) => {
3427                self.generate_ephemeral_column_constraint(e)
3428            }
3429            Expression::EqualNull(e) => self.generate_equal_null(e),
3430            Expression::EuclideanDistance(e) => self.generate_euclidean_distance(e),
3431            Expression::ExecuteAsProperty(e) => self.generate_execute_as_property(e),
3432            Expression::Export(e) => self.generate_export(e),
3433            Expression::ExternalProperty(e) => self.generate_external_property(e),
3434            Expression::FallbackProperty(e) => self.generate_fallback_property(e),
3435            Expression::FarmFingerprint(e) => self.generate_farm_fingerprint(e),
3436            Expression::FeaturesAtTime(e) => self.generate_features_at_time(e),
3437            Expression::Fetch(e) => self.generate_fetch(e),
3438            Expression::FileFormatProperty(e) => self.generate_file_format_property(e),
3439            Expression::Filter(e) => self.generate_filter(e),
3440            Expression::Float64(e) => self.generate_float64(e),
3441            Expression::ForIn(e) => self.generate_for_in(e),
3442            Expression::ForeignKey(e) => self.generate_foreign_key(e),
3443            Expression::Format(e) => self.generate_format(e),
3444            Expression::FormatPhrase(e) => self.generate_format_phrase(e),
3445            Expression::FreespaceProperty(e) => self.generate_freespace_property(e),
3446            Expression::From(e) => self.generate_from(e),
3447            Expression::FromBase(e) => self.generate_from_base(e),
3448            Expression::FromTimeZone(e) => self.generate_from_time_zone(e),
3449            Expression::GapFill(e) => self.generate_gap_fill(e),
3450            Expression::GenerateDateArray(e) => self.generate_generate_date_array(e),
3451            Expression::GenerateEmbedding(e) => self.generate_generate_embedding(e),
3452            Expression::GenerateSeries(e) => self.generate_generate_series(e),
3453            Expression::GenerateTimestampArray(e) => self.generate_generate_timestamp_array(e),
3454            Expression::GeneratedAsIdentityColumnConstraint(e) => {
3455                self.generate_generated_as_identity_column_constraint(e)
3456            }
3457            Expression::GeneratedAsRowColumnConstraint(e) => {
3458                self.generate_generated_as_row_column_constraint(e)
3459            }
3460            Expression::Get(e) => self.generate_get(e),
3461            Expression::GetExtract(e) => self.generate_get_extract(e),
3462            Expression::Getbit(e) => self.generate_getbit(e),
3463            Expression::GrantPrincipal(e) => self.generate_grant_principal(e),
3464            Expression::GrantPrivilege(e) => self.generate_grant_privilege(e),
3465            Expression::Group(e) => self.generate_group(e),
3466            Expression::GroupBy(e) => self.generate_group_by(e),
3467            Expression::Grouping(e) => self.generate_grouping(e),
3468            Expression::GroupingId(e) => self.generate_grouping_id(e),
3469            Expression::GroupingSets(e) => self.generate_grouping_sets(e),
3470            Expression::HashAgg(e) => self.generate_hash_agg(e),
3471            Expression::Having(e) => self.generate_having(e),
3472            Expression::HavingMax(e) => self.generate_having_max(e),
3473            Expression::Heredoc(e) => self.generate_heredoc(e),
3474            Expression::HexEncode(e) => self.generate_hex_encode(e),
3475            Expression::Hll(e) => self.generate_hll(e),
3476            Expression::InOutColumnConstraint(e) => self.generate_in_out_column_constraint(e),
3477            Expression::IncludeProperty(e) => self.generate_include_property(e),
3478            Expression::Index(e) => self.generate_index(e),
3479            Expression::IndexColumnConstraint(e) => self.generate_index_column_constraint(e),
3480            Expression::IndexConstraintOption(e) => self.generate_index_constraint_option(e),
3481            Expression::IndexParameters(e) => self.generate_index_parameters(e),
3482            Expression::IndexTableHint(e) => self.generate_index_table_hint(e),
3483            Expression::InheritsProperty(e) => self.generate_inherits_property(e),
3484            Expression::InputModelProperty(e) => self.generate_input_model_property(e),
3485            Expression::InputOutputFormat(e) => self.generate_input_output_format(e),
3486            Expression::Install(e) => self.generate_install(e),
3487            Expression::IntervalOp(e) => self.generate_interval_op(e),
3488            Expression::IntervalSpan(e) => self.generate_interval_span(e),
3489            Expression::IntoClause(e) => self.generate_into_clause(e),
3490            Expression::Introducer(e) => self.generate_introducer(e),
3491            Expression::IsolatedLoadingProperty(e) => self.generate_isolated_loading_property(e),
3492            Expression::JSON(e) => self.generate_json(e),
3493            Expression::JSONArray(e) => self.generate_json_array(e),
3494            Expression::JSONArrayAgg(e) => self.generate_json_array_agg_struct(e),
3495            Expression::JSONArrayAppend(e) => self.generate_json_array_append(e),
3496            Expression::JSONArrayContains(e) => self.generate_json_array_contains(e),
3497            Expression::JSONArrayInsert(e) => self.generate_json_array_insert(e),
3498            Expression::JSONBExists(e) => self.generate_jsonb_exists(e),
3499            Expression::JSONBExtractScalar(e) => self.generate_jsonb_extract_scalar(e),
3500            Expression::JSONBObjectAgg(e) => self.generate_jsonb_object_agg(e),
3501            Expression::JSONObjectAgg(e) => self.generate_json_object_agg_struct(e),
3502            Expression::JSONColumnDef(e) => self.generate_json_column_def(e),
3503            Expression::JSONExists(e) => self.generate_json_exists(e),
3504            Expression::JSONCast(e) => self.generate_json_cast(e),
3505            Expression::JSONExtract(e) => self.generate_json_extract_path(e),
3506            Expression::JSONExtractArray(e) => self.generate_json_extract_array(e),
3507            Expression::JSONExtractQuote(e) => self.generate_json_extract_quote(e),
3508            Expression::JSONExtractScalar(e) => self.generate_json_extract_scalar(e),
3509            Expression::JSONFormat(e) => self.generate_json_format(e),
3510            Expression::JSONKeyValue(e) => self.generate_json_key_value(e),
3511            Expression::JSONKeys(e) => self.generate_json_keys(e),
3512            Expression::JSONKeysAtDepth(e) => self.generate_json_keys_at_depth(e),
3513            Expression::JSONPath(e) => self.generate_json_path_expr(e),
3514            Expression::JSONPathFilter(e) => self.generate_json_path_filter(e),
3515            Expression::JSONPathKey(e) => self.generate_json_path_key(e),
3516            Expression::JSONPathRecursive(e) => self.generate_json_path_recursive(e),
3517            Expression::JSONPathRoot(_) => self.generate_json_path_root(),
3518            Expression::JSONPathScript(e) => self.generate_json_path_script(e),
3519            Expression::JSONPathSelector(e) => self.generate_json_path_selector(e),
3520            Expression::JSONPathSlice(e) => self.generate_json_path_slice(e),
3521            Expression::JSONPathSubscript(e) => self.generate_json_path_subscript(e),
3522            Expression::JSONPathUnion(e) => self.generate_json_path_union(e),
3523            Expression::JSONRemove(e) => self.generate_json_remove(e),
3524            Expression::JSONSchema(e) => self.generate_json_schema(e),
3525            Expression::JSONSet(e) => self.generate_json_set(e),
3526            Expression::JSONStripNulls(e) => self.generate_json_strip_nulls(e),
3527            Expression::JSONTable(e) => self.generate_json_table(e),
3528            Expression::JSONType(e) => self.generate_json_type(e),
3529            Expression::JSONValue(e) => self.generate_json_value(e),
3530            Expression::JSONValueArray(e) => self.generate_json_value_array(e),
3531            Expression::JarowinklerSimilarity(e) => self.generate_jarowinkler_similarity(e),
3532            Expression::JoinHint(e) => self.generate_join_hint(e),
3533            Expression::JournalProperty(e) => self.generate_journal_property(e),
3534            Expression::LanguageProperty(e) => self.generate_language_property(e),
3535            Expression::Lateral(e) => self.generate_lateral(e),
3536            Expression::LikeProperty(e) => self.generate_like_property(e),
3537            Expression::Limit(e) => self.generate_limit(e),
3538            Expression::LimitOptions(e) => self.generate_limit_options(e),
3539            Expression::List(e) => self.generate_list(e),
3540            Expression::ToMap(e) => self.generate_tomap(e),
3541            Expression::Localtime(e) => self.generate_localtime(e),
3542            Expression::Localtimestamp(e) => self.generate_localtimestamp(e),
3543            Expression::LocationProperty(e) => self.generate_location_property(e),
3544            Expression::Lock(e) => self.generate_lock(e),
3545            Expression::LockProperty(e) => self.generate_lock_property(e),
3546            Expression::LockingProperty(e) => self.generate_locking_property(e),
3547            Expression::LockingStatement(e) => self.generate_locking_statement(e),
3548            Expression::LogProperty(e) => self.generate_log_property(e),
3549            Expression::MD5Digest(e) => self.generate_md5_digest(e),
3550            Expression::MLForecast(e) => self.generate_ml_forecast(e),
3551            Expression::MLTranslate(e) => self.generate_ml_translate(e),
3552            Expression::MakeInterval(e) => self.generate_make_interval(e),
3553            Expression::ManhattanDistance(e) => self.generate_manhattan_distance(e),
3554            Expression::Map(e) => self.generate_map(e),
3555            Expression::MapCat(e) => self.generate_map_cat(e),
3556            Expression::MapDelete(e) => self.generate_map_delete(e),
3557            Expression::MapInsert(e) => self.generate_map_insert(e),
3558            Expression::MapPick(e) => self.generate_map_pick(e),
3559            Expression::MaskingPolicyColumnConstraint(e) => {
3560                self.generate_masking_policy_column_constraint(e)
3561            }
3562            Expression::MatchAgainst(e) => self.generate_match_against(e),
3563            Expression::MatchRecognizeMeasure(e) => self.generate_match_recognize_measure(e),
3564            Expression::MaterializedProperty(e) => self.generate_materialized_property(e),
3565            Expression::Merge(e) => self.generate_merge(e),
3566            Expression::MergeBlockRatioProperty(e) => self.generate_merge_block_ratio_property(e),
3567            Expression::MergeTreeTTL(e) => self.generate_merge_tree_ttl(e),
3568            Expression::MergeTreeTTLAction(e) => self.generate_merge_tree_ttl_action(e),
3569            Expression::Minhash(e) => self.generate_minhash(e),
3570            Expression::ModelAttribute(e) => self.generate_model_attribute(e),
3571            Expression::Monthname(e) => self.generate_monthname(e),
3572            Expression::MultitableInserts(e) => self.generate_multitable_inserts(e),
3573            Expression::NextValueFor(e) => self.generate_next_value_for(e),
3574            Expression::Normal(e) => self.generate_normal(e),
3575            Expression::Normalize(e) => self.generate_normalize(e),
3576            Expression::NotNullColumnConstraint(e) => self.generate_not_null_column_constraint(e),
3577            Expression::Nullif(e) => self.generate_nullif(e),
3578            Expression::NumberToStr(e) => self.generate_number_to_str(e),
3579            Expression::ObjectAgg(e) => self.generate_object_agg(e),
3580            Expression::ObjectIdentifier(e) => self.generate_object_identifier(e),
3581            Expression::ObjectInsert(e) => self.generate_object_insert(e),
3582            Expression::Offset(e) => self.generate_offset(e),
3583            Expression::Qualify(e) => self.generate_qualify(e),
3584            Expression::OnCluster(e) => self.generate_on_cluster(e),
3585            Expression::OnCommitProperty(e) => self.generate_on_commit_property(e),
3586            Expression::OnCondition(e) => self.generate_on_condition(e),
3587            Expression::OnConflict(e) => self.generate_on_conflict(e),
3588            Expression::OnProperty(e) => self.generate_on_property(e),
3589            Expression::Opclass(e) => self.generate_opclass(e),
3590            Expression::OpenJSON(e) => self.generate_open_json(e),
3591            Expression::OpenJSONColumnDef(e) => self.generate_open_json_column_def(e),
3592            Expression::Operator(e) => self.generate_operator(e),
3593            Expression::OrderBy(e) => self.generate_order_by(e),
3594            Expression::OutputModelProperty(e) => self.generate_output_model_property(e),
3595            Expression::OverflowTruncateBehavior(e) => self.generate_overflow_truncate_behavior(e),
3596            Expression::ParameterizedAgg(e) => self.generate_parameterized_agg(e),
3597            Expression::ParseDatetime(e) => self.generate_parse_datetime(e),
3598            Expression::ParseIp(e) => self.generate_parse_ip(e),
3599            Expression::ParseJSON(e) => self.generate_parse_json(e),
3600            Expression::ParseTime(e) => self.generate_parse_time(e),
3601            Expression::ParseUrl(e) => self.generate_parse_url(e),
3602            Expression::Partition(e) => self.generate_partition_expr(e),
3603            Expression::PartitionBoundSpec(e) => self.generate_partition_bound_spec(e),
3604            Expression::PartitionByListProperty(e) => self.generate_partition_by_list_property(e),
3605            Expression::PartitionByRangeProperty(e) => self.generate_partition_by_range_property(e),
3606            Expression::PartitionByRangePropertyDynamic(e) => {
3607                self.generate_partition_by_range_property_dynamic(e)
3608            }
3609            Expression::PartitionByTruncate(e) => self.generate_partition_by_truncate(e),
3610            Expression::PartitionList(e) => self.generate_partition_list(e),
3611            Expression::PartitionRange(e) => self.generate_partition_range(e),
3612            Expression::PartitionedByBucket(e) => self.generate_partitioned_by_bucket(e),
3613            Expression::PartitionedByProperty(e) => self.generate_partitioned_by_property(e),
3614            Expression::PartitionedOfProperty(e) => self.generate_partitioned_of_property(e),
3615            Expression::PeriodForSystemTimeConstraint(e) => {
3616                self.generate_period_for_system_time_constraint(e)
3617            }
3618            Expression::PivotAlias(e) => self.generate_pivot_alias(e),
3619            Expression::PivotAny(e) => self.generate_pivot_any(e),
3620            Expression::Predict(e) => self.generate_predict(e),
3621            Expression::PreviousDay(e) => self.generate_previous_day(e),
3622            Expression::PrimaryKey(e) => self.generate_primary_key(e),
3623            Expression::PrimaryKeyColumnConstraint(e) => {
3624                self.generate_primary_key_column_constraint(e)
3625            }
3626            Expression::PathColumnConstraint(e) => self.generate_path_column_constraint(e),
3627            Expression::ProjectionDef(e) => self.generate_projection_def(e),
3628            Expression::Properties(e) => self.generate_properties(e),
3629            Expression::Property(e) => self.generate_property(e),
3630            Expression::PseudoType(e) => self.generate_pseudo_type(e),
3631            Expression::Put(e) => self.generate_put(e),
3632            Expression::Quantile(e) => self.generate_quantile(e),
3633            Expression::QueryBand(e) => self.generate_query_band(e),
3634            Expression::QueryOption(e) => self.generate_query_option(e),
3635            Expression::QueryTransform(e) => self.generate_query_transform(e),
3636            Expression::Randn(e) => self.generate_randn(e),
3637            Expression::Randstr(e) => self.generate_randstr(e),
3638            Expression::RangeBucket(e) => self.generate_range_bucket(e),
3639            Expression::RangeN(e) => self.generate_range_n(e),
3640            Expression::ReadCSV(e) => self.generate_read_csv(e),
3641            Expression::ReadParquet(e) => self.generate_read_parquet(e),
3642            Expression::RecursiveWithSearch(e) => self.generate_recursive_with_search(e),
3643            Expression::Reduce(e) => self.generate_reduce(e),
3644            Expression::Reference(e) => self.generate_reference(e),
3645            Expression::Refresh(e) => self.generate_refresh(e),
3646            Expression::RefreshTriggerProperty(e) => self.generate_refresh_trigger_property(e),
3647            Expression::RegexpCount(e) => self.generate_regexp_count(e),
3648            Expression::RegexpExtractAll(e) => self.generate_regexp_extract_all(e),
3649            Expression::RegexpFullMatch(e) => self.generate_regexp_full_match(e),
3650            Expression::RegexpILike(e) => self.generate_regexp_i_like(e),
3651            Expression::RegexpInstr(e) => self.generate_regexp_instr(e),
3652            Expression::RegexpSplit(e) => self.generate_regexp_split(e),
3653            Expression::RegrAvgx(e) => self.generate_regr_avgx(e),
3654            Expression::RegrAvgy(e) => self.generate_regr_avgy(e),
3655            Expression::RegrCount(e) => self.generate_regr_count(e),
3656            Expression::RegrIntercept(e) => self.generate_regr_intercept(e),
3657            Expression::RegrR2(e) => self.generate_regr_r2(e),
3658            Expression::RegrSlope(e) => self.generate_regr_slope(e),
3659            Expression::RegrSxx(e) => self.generate_regr_sxx(e),
3660            Expression::RegrSxy(e) => self.generate_regr_sxy(e),
3661            Expression::RegrSyy(e) => self.generate_regr_syy(e),
3662            Expression::RegrValx(e) => self.generate_regr_valx(e),
3663            Expression::RegrValy(e) => self.generate_regr_valy(e),
3664            Expression::RemoteWithConnectionModelProperty(e) => {
3665                self.generate_remote_with_connection_model_property(e)
3666            }
3667            Expression::RenameColumn(e) => self.generate_rename_column(e),
3668            Expression::ReplacePartition(e) => self.generate_replace_partition(e),
3669            Expression::Returning(e) => self.generate_returning(e),
3670            Expression::ReturnsProperty(e) => self.generate_returns_property(e),
3671            Expression::Rollback(e) => self.generate_rollback(e),
3672            Expression::Rollup(e) => self.generate_rollup(e),
3673            Expression::RowFormatDelimitedProperty(e) => {
3674                self.generate_row_format_delimited_property(e)
3675            }
3676            Expression::RowFormatProperty(e) => self.generate_row_format_property(e),
3677            Expression::RowFormatSerdeProperty(e) => self.generate_row_format_serde_property(e),
3678            Expression::SHA2(e) => self.generate_sha2(e),
3679            Expression::SHA2Digest(e) => self.generate_sha2_digest(e),
3680            Expression::SafeAdd(e) => self.generate_safe_add(e),
3681            Expression::SafeDivide(e) => self.generate_safe_divide(e),
3682            Expression::SafeMultiply(e) => self.generate_safe_multiply(e),
3683            Expression::SafeSubtract(e) => self.generate_safe_subtract(e),
3684            Expression::SampleProperty(e) => self.generate_sample_property(e),
3685            Expression::Schema(e) => self.generate_schema(e),
3686            Expression::SchemaCommentProperty(e) => self.generate_schema_comment_property(e),
3687            Expression::ScopeResolution(e) => self.generate_scope_resolution(e),
3688            Expression::Search(e) => self.generate_search(e),
3689            Expression::SearchIp(e) => self.generate_search_ip(e),
3690            Expression::SecurityProperty(e) => self.generate_security_property(e),
3691            Expression::SemanticView(e) => self.generate_semantic_view(e),
3692            Expression::SequenceProperties(e) => self.generate_sequence_properties(e),
3693            Expression::SerdeProperties(e) => self.generate_serde_properties(e),
3694            Expression::SessionParameter(e) => self.generate_session_parameter(e),
3695            Expression::Set(e) => self.generate_set(e),
3696            Expression::SetConfigProperty(e) => self.generate_set_config_property(e),
3697            Expression::SetItem(e) => self.generate_set_item(e),
3698            Expression::SetOperation(e) => self.generate_set_operation(e),
3699            Expression::SetProperty(e) => self.generate_set_property(e),
3700            Expression::SettingsProperty(e) => self.generate_settings_property(e),
3701            Expression::SharingProperty(e) => self.generate_sharing_property(e),
3702            Expression::Slice(e) => self.generate_slice(e),
3703            Expression::SortArray(e) => self.generate_sort_array(e),
3704            Expression::SortBy(e) => self.generate_sort_by(e),
3705            Expression::SortKeyProperty(e) => self.generate_sort_key_property(e),
3706            Expression::SplitPart(e) => self.generate_split_part(e),
3707            Expression::SqlReadWriteProperty(e) => self.generate_sql_read_write_property(e),
3708            Expression::SqlSecurityProperty(e) => self.generate_sql_security_property(e),
3709            Expression::StDistance(e) => self.generate_st_distance(e),
3710            Expression::StPoint(e) => self.generate_st_point(e),
3711            Expression::StabilityProperty(e) => self.generate_stability_property(e),
3712            Expression::StandardHash(e) => self.generate_standard_hash(e),
3713            Expression::StorageHandlerProperty(e) => self.generate_storage_handler_property(e),
3714            Expression::StrPosition(e) => self.generate_str_position(e),
3715            Expression::StrToDate(e) => self.generate_str_to_date(e),
3716            Expression::DateStrToDate(f) => self.generate_simple_func("DATE_STR_TO_DATE", &f.this),
3717            Expression::DateToDateStr(f) => self.generate_simple_func("DATE_TO_DATE_STR", &f.this),
3718            Expression::StrToMap(e) => self.generate_str_to_map(e),
3719            Expression::StrToTime(e) => self.generate_str_to_time(e),
3720            Expression::StrToUnix(e) => self.generate_str_to_unix(e),
3721            Expression::StringToArray(e) => self.generate_string_to_array(e),
3722            Expression::Struct(e) => self.generate_struct(e),
3723            Expression::Stuff(e) => self.generate_stuff(e),
3724            Expression::SubstringIndex(e) => self.generate_substring_index(e),
3725            Expression::Summarize(e) => self.generate_summarize(e),
3726            Expression::Systimestamp(e) => self.generate_systimestamp(e),
3727            Expression::TableAlias(e) => self.generate_table_alias(e),
3728            Expression::TableFromRows(e) => self.generate_table_from_rows(e),
3729            Expression::RowsFrom(e) => self.generate_rows_from(e),
3730            Expression::TableSample(e) => self.generate_table_sample(e),
3731            Expression::Tag(e) => self.generate_tag(e),
3732            Expression::Tags(e) => self.generate_tags(e),
3733            Expression::TemporaryProperty(e) => self.generate_temporary_property(e),
3734            Expression::Time(e) => self.generate_time_func(e),
3735            Expression::TimeAdd(e) => self.generate_time_add(e),
3736            Expression::TimeDiff(e) => self.generate_time_diff(e),
3737            Expression::TimeFromParts(e) => self.generate_time_from_parts(e),
3738            Expression::TimeSlice(e) => self.generate_time_slice(e),
3739            Expression::TimeStrToDate(e) => self.generate_time_str_to_date(e),
3740            Expression::TimeStrToTime(e) => self.generate_time_str_to_time(e),
3741            Expression::TimeSub(e) => self.generate_time_sub(e),
3742            Expression::TimeToStr(e) => self.generate_time_to_str(e),
3743            Expression::TimeToUnix(e) => self.generate_time_to_unix(e),
3744            Expression::TimeTrunc(e) => self.generate_time_trunc(e),
3745            Expression::TimeUnit(e) => self.generate_time_unit(e),
3746            Expression::Timestamp(e) => self.generate_timestamp_func(e),
3747            Expression::TimestampAdd(e) => self.generate_timestamp_add(e),
3748            Expression::TimestampDiff(e) => self.generate_timestamp_diff(e),
3749            Expression::TimestampFromParts(e) => self.generate_timestamp_from_parts(e),
3750            Expression::TimestampSub(e) => self.generate_timestamp_sub(e),
3751            Expression::TimestampTzFromParts(e) => self.generate_timestamp_tz_from_parts(e),
3752            Expression::ToBinary(e) => self.generate_to_binary(e),
3753            Expression::ToBoolean(e) => self.generate_to_boolean(e),
3754            Expression::ToChar(e) => self.generate_to_char(e),
3755            Expression::ToDecfloat(e) => self.generate_to_decfloat(e),
3756            Expression::ToDouble(e) => self.generate_to_double(e),
3757            Expression::ToFile(e) => self.generate_to_file(e),
3758            Expression::ToNumber(e) => self.generate_to_number(e),
3759            Expression::ToTableProperty(e) => self.generate_to_table_property(e),
3760            Expression::Transaction(e) => self.generate_transaction(e),
3761            Expression::Transform(e) => self.generate_transform(e),
3762            Expression::TransformModelProperty(e) => self.generate_transform_model_property(e),
3763            Expression::TransientProperty(e) => self.generate_transient_property(e),
3764            Expression::Translate(e) => self.generate_translate(e),
3765            Expression::TranslateCharacters(e) => self.generate_translate_characters(e),
3766            Expression::TruncateTable(e) => self.generate_truncate_table(e),
3767            Expression::TryBase64DecodeBinary(e) => self.generate_try_base64_decode_binary(e),
3768            Expression::TryBase64DecodeString(e) => self.generate_try_base64_decode_string(e),
3769            Expression::TryToDecfloat(e) => self.generate_try_to_decfloat(e),
3770            Expression::TsOrDsAdd(e) => self.generate_ts_or_ds_add(e),
3771            Expression::TsOrDsDiff(e) => self.generate_ts_or_ds_diff(e),
3772            Expression::TsOrDsToDate(e) => self.generate_ts_or_ds_to_date(e),
3773            Expression::TsOrDsToTime(e) => self.generate_ts_or_ds_to_time(e),
3774            Expression::Unhex(e) => self.generate_unhex(e),
3775            Expression::UnicodeString(e) => self.generate_unicode_string(e),
3776            Expression::Uniform(e) => self.generate_uniform(e),
3777            Expression::UniqueColumnConstraint(e) => self.generate_unique_column_constraint(e),
3778            Expression::UniqueKeyProperty(e) => self.generate_unique_key_property(e),
3779            Expression::RollupProperty(e) => self.generate_rollup_property(e),
3780            Expression::UnixToStr(e) => self.generate_unix_to_str(e),
3781            Expression::UnixToTime(e) => self.generate_unix_to_time(e),
3782            Expression::UnpivotColumns(e) => self.generate_unpivot_columns(e),
3783            Expression::UserDefinedFunction(e) => self.generate_user_defined_function(e),
3784            Expression::UsingTemplateProperty(e) => self.generate_using_template_property(e),
3785            Expression::UtcTime(e) => self.generate_utc_time(e),
3786            Expression::UtcTimestamp(e) => self.generate_utc_timestamp(e),
3787            Expression::Uuid(e) => self.generate_uuid(e),
3788            Expression::Var(v) => {
3789                if matches!(self.config.dialect, Some(DialectType::MySQL))
3790                    && v.this.len() > 2
3791                    && (v.this.starts_with("0x") || v.this.starts_with("0X"))
3792                    && !v.this[2..].chars().all(|c| c.is_ascii_hexdigit())
3793                {
3794                    return self.generate_identifier(&Identifier {
3795                        name: v.this.clone(),
3796                        quoted: true,
3797                        trailing_comments: Vec::new(),
3798                    });
3799                }
3800                self.write(&v.this);
3801                Ok(())
3802            }
3803            Expression::Variadic(e) => {
3804                self.write_keyword("VARIADIC");
3805                self.write_space();
3806                self.generate_expression(&e.this)?;
3807                Ok(())
3808            }
3809            Expression::VarMap(e) => self.generate_var_map(e),
3810            Expression::VectorSearch(e) => self.generate_vector_search(e),
3811            Expression::Version(e) => self.generate_version(e),
3812            Expression::ViewAttributeProperty(e) => self.generate_view_attribute_property(e),
3813            Expression::VolatileProperty(e) => self.generate_volatile_property(e),
3814            Expression::WatermarkColumnConstraint(e) => {
3815                self.generate_watermark_column_constraint(e)
3816            }
3817            Expression::Week(e) => self.generate_week(e),
3818            Expression::When(e) => self.generate_when(e),
3819            Expression::Whens(e) => self.generate_whens(e),
3820            Expression::Where(e) => self.generate_where(e),
3821            Expression::WidthBucket(e) => self.generate_width_bucket(e),
3822            Expression::Window(e) => self.generate_window(e),
3823            Expression::WindowSpec(e) => self.generate_window_spec(e),
3824            Expression::WithDataProperty(e) => self.generate_with_data_property(e),
3825            Expression::WithFill(e) => self.generate_with_fill(e),
3826            Expression::WithJournalTableProperty(e) => self.generate_with_journal_table_property(e),
3827            Expression::WithOperator(e) => self.generate_with_operator(e),
3828            Expression::WithProcedureOptions(e) => self.generate_with_procedure_options(e),
3829            Expression::WithSchemaBindingProperty(e) => {
3830                self.generate_with_schema_binding_property(e)
3831            }
3832            Expression::WithSystemVersioningProperty(e) => {
3833                self.generate_with_system_versioning_property(e)
3834            }
3835            Expression::WithTableHint(e) => self.generate_with_table_hint(e),
3836            Expression::XMLElement(e) => self.generate_xml_element(e),
3837            Expression::XMLGet(e) => self.generate_xml_get(e),
3838            Expression::XMLKeyValueOption(e) => self.generate_xml_key_value_option(e),
3839            Expression::XMLTable(e) => self.generate_xml_table(e),
3840            Expression::Xor(e) => self.generate_xor(e),
3841            Expression::Zipf(e) => self.generate_zipf(e),
3842            _ => {
3843                // Fallback for unimplemented expressions
3844                self.write(&format!("/* unimplemented: {:?} */", expr));
3845                Ok(())
3846            }
3847        }
3848    }
3849
3850    fn generate_select(&mut self, select: &Select) -> Result<()> {
3851        use crate::dialects::DialectType;
3852
3853        // Output leading comments before SELECT
3854        for comment in &select.leading_comments {
3855            self.write_formatted_comment(comment);
3856            self.write(" ");
3857        }
3858
3859        // WITH clause
3860        if let Some(with) = &select.with {
3861            self.generate_with(with)?;
3862            if self.config.pretty {
3863                self.write_newline();
3864                self.write_indent();
3865            } else {
3866                self.write_space();
3867            }
3868        }
3869
3870        // Output post-SELECT comments (comments that appeared after SELECT keyword)
3871        // These are output BEFORE SELECT, as Python SQLGlot normalizes them this way
3872        for comment in &select.post_select_comments {
3873            self.write_formatted_comment(comment);
3874            self.write(" ");
3875        }
3876
3877        self.write_keyword("SELECT");
3878
3879        // Generate query hint if present /*+ ... */
3880        if let Some(hint) = &select.hint {
3881            self.generate_hint(hint)?;
3882        }
3883
3884        // For SQL Server, convert LIMIT to TOP (structural transformation)
3885        // But only when there's no OFFSET (otherwise use OFFSET/FETCH syntax)
3886        // TOP clause (SQL Server style - before DISTINCT)
3887        let use_top_from_limit = matches!(self.config.dialect, Some(DialectType::TSQL))
3888            && select.top.is_none()
3889            && select.limit.is_some()
3890            && select.offset.is_none(); // Don't use TOP when there's OFFSET
3891
3892        // For TOP-supporting dialects: DISTINCT before TOP
3893        // For non-TOP dialects: TOP is converted to LIMIT later; DISTINCT goes here
3894        let is_top_dialect = matches!(
3895            self.config.dialect,
3896            Some(DialectType::TSQL) | Some(DialectType::Teradata) | Some(DialectType::Fabric)
3897        );
3898
3899        if select.distinct && (is_top_dialect || select.top.is_some()) {
3900            self.write_space();
3901            self.write_keyword("DISTINCT");
3902        }
3903
3904        if is_top_dialect {
3905            if let Some(top) = &select.top {
3906                self.write_space();
3907                self.write_keyword("TOP");
3908                if top.parenthesized {
3909                    self.write(" (");
3910                    self.generate_expression(&top.this)?;
3911                    self.write(")");
3912                } else {
3913                    self.write_space();
3914                    self.generate_expression(&top.this)?;
3915                }
3916                if top.percent {
3917                    self.write_space();
3918                    self.write_keyword("PERCENT");
3919                }
3920                if top.with_ties {
3921                    self.write_space();
3922                    self.write_keyword("WITH TIES");
3923                }
3924            } else if use_top_from_limit {
3925                // Convert LIMIT to TOP for SQL Server (only when no OFFSET)
3926                if let Some(limit) = &select.limit {
3927                    self.write_space();
3928                    self.write_keyword("TOP");
3929                    // Use parentheses for complex expressions, but not for simple literals
3930                    let is_simple_literal =
3931                        matches!(&limit.this, Expression::Literal(Literal::Number(_)));
3932                    if is_simple_literal {
3933                        self.write_space();
3934                        self.generate_expression(&limit.this)?;
3935                    } else {
3936                        self.write(" (");
3937                        self.generate_expression(&limit.this)?;
3938                        self.write(")");
3939                    }
3940                }
3941            }
3942        }
3943
3944        if select.distinct && !is_top_dialect && select.top.is_none() {
3945            self.write_space();
3946            self.write_keyword("DISTINCT");
3947        }
3948
3949        // DISTINCT ON clause (PostgreSQL)
3950        if let Some(distinct_on) = &select.distinct_on {
3951            self.write_space();
3952            self.write_keyword("ON");
3953            self.write(" (");
3954            for (i, expr) in distinct_on.iter().enumerate() {
3955                if i > 0 {
3956                    self.write(", ");
3957                }
3958                self.generate_expression(expr)?;
3959            }
3960            self.write(")");
3961        }
3962
3963        // MySQL operation modifiers (HIGH_PRIORITY, STRAIGHT_JOIN, SQL_CALC_FOUND_ROWS, etc.)
3964        for modifier in &select.operation_modifiers {
3965            self.write_space();
3966            self.write_keyword(modifier);
3967        }
3968
3969        // BigQuery SELECT AS STRUCT / SELECT AS VALUE
3970        if let Some(kind) = &select.kind {
3971            self.write_space();
3972            self.write_keyword("AS");
3973            self.write_space();
3974            self.write_keyword(kind);
3975        }
3976
3977        // Expressions (only if there are any)
3978        if !select.expressions.is_empty() {
3979            if self.config.pretty {
3980                self.write_newline();
3981                self.indent_level += 1;
3982            } else {
3983                self.write_space();
3984            }
3985        }
3986
3987        for (i, expr) in select.expressions.iter().enumerate() {
3988            if i > 0 {
3989                self.write(",");
3990                if self.config.pretty {
3991                    self.write_newline();
3992                } else {
3993                    self.write_space();
3994                }
3995            }
3996            if self.config.pretty {
3997                self.write_indent();
3998            }
3999            self.generate_expression(expr)?;
4000        }
4001
4002        if self.config.pretty && !select.expressions.is_empty() {
4003            self.indent_level -= 1;
4004        }
4005
4006        // INTO clause (SELECT ... INTO table_name)
4007        // Also handles Oracle PL/SQL: BULK COLLECT INTO v1, v2, ...
4008        if let Some(into) = &select.into {
4009            if self.config.pretty {
4010                self.write_newline();
4011                self.write_indent();
4012            } else {
4013                self.write_space();
4014            }
4015            if into.bulk_collect {
4016                self.write_keyword("BULK COLLECT INTO");
4017            } else {
4018                self.write_keyword("INTO");
4019            }
4020            if into.temporary {
4021                self.write_space();
4022                self.write_keyword("TEMPORARY");
4023            }
4024            if into.unlogged {
4025                self.write_space();
4026                self.write_keyword("UNLOGGED");
4027            }
4028            self.write_space();
4029            // If we have multiple expressions, output them comma-separated
4030            if !into.expressions.is_empty() {
4031                for (i, expr) in into.expressions.iter().enumerate() {
4032                    if i > 0 {
4033                        self.write(", ");
4034                    }
4035                    self.generate_expression(expr)?;
4036                }
4037            } else {
4038                self.generate_expression(&into.this)?;
4039            }
4040        }
4041
4042        // FROM clause
4043        if let Some(from) = &select.from {
4044            if self.config.pretty {
4045                self.write_newline();
4046                self.write_indent();
4047            } else {
4048                self.write_space();
4049            }
4050            self.write_keyword("FROM");
4051            self.write_space();
4052
4053            // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax for multiple tables
4054            // But keep commas when TABLESAMPLE is present (Spark/Hive handle TABLESAMPLE differently with commas)
4055            // Also keep commas when the source dialect is Generic/None and target is one of these dialects
4056            // (Python sqlglot: the Hive/Spark parser marks comma joins as CROSS, but Generic parser keeps them implicit)
4057            let has_tablesample = from
4058                .expressions
4059                .iter()
4060                .any(|e| matches!(e, Expression::TableSample(_)));
4061            let is_cross_join_dialect = matches!(
4062                self.config.dialect,
4063                Some(DialectType::BigQuery)
4064                    | Some(DialectType::Hive)
4065                    | Some(DialectType::Spark)
4066                    | Some(DialectType::Databricks)
4067                    | Some(DialectType::SQLite)
4068                    | Some(DialectType::ClickHouse)
4069            );
4070            // Skip CROSS JOIN conversion when source is Generic/None and target is a CROSS JOIN dialect
4071            // This matches Python sqlglot where comma-to-CROSS-JOIN is done in the dialect's parser, not generator
4072            let source_is_same_as_target = self.config.source_dialect.is_some()
4073                && self.config.source_dialect == self.config.dialect;
4074            let source_is_cross_join_dialect = matches!(
4075                self.config.source_dialect,
4076                Some(DialectType::BigQuery)
4077                    | Some(DialectType::Hive)
4078                    | Some(DialectType::Spark)
4079                    | Some(DialectType::Databricks)
4080                    | Some(DialectType::SQLite)
4081                    | Some(DialectType::ClickHouse)
4082            );
4083            let use_cross_join = !has_tablesample
4084                && is_cross_join_dialect
4085                && (source_is_same_as_target
4086                    || source_is_cross_join_dialect
4087                    || self.config.source_dialect.is_none());
4088
4089            // Snowflake wraps standalone VALUES in FROM clause with parentheses
4090            let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
4091
4092            for (i, expr) in from.expressions.iter().enumerate() {
4093                if i > 0 {
4094                    if use_cross_join {
4095                        self.write(" CROSS JOIN ");
4096                    } else {
4097                        self.write(", ");
4098                    }
4099                }
4100                if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
4101                    self.write("(");
4102                    self.generate_expression(expr)?;
4103                    self.write(")");
4104                } else {
4105                    self.generate_expression(expr)?;
4106                }
4107            }
4108        }
4109
4110        // JOINs - handle nested join structure for pretty printing
4111        // Deferred-condition joins "own" the non-deferred joins that follow them
4112        // until the next deferred join or end of list
4113        if self.config.pretty {
4114            self.generate_joins_with_nesting(&select.joins)?;
4115        } else {
4116            for join in &select.joins {
4117                self.generate_join(join)?;
4118            }
4119            // Output deferred ON/USING conditions (right-to-left, which is reverse order)
4120            for join in select.joins.iter().rev() {
4121                if join.deferred_condition {
4122                    self.generate_join_condition(join)?;
4123                }
4124            }
4125        }
4126
4127        // LATERAL VIEW clauses (Hive/Spark)
4128        for lateral_view in &select.lateral_views {
4129            self.generate_lateral_view(lateral_view)?;
4130        }
4131
4132        // PREWHERE (ClickHouse)
4133        if let Some(prewhere) = &select.prewhere {
4134            self.write_clause_condition("PREWHERE", prewhere)?;
4135        }
4136
4137        // WHERE
4138        if let Some(where_clause) = &select.where_clause {
4139            self.write_clause_condition("WHERE", &where_clause.this)?;
4140        }
4141
4142        // CONNECT BY (Oracle hierarchical queries)
4143        if let Some(connect) = &select.connect {
4144            self.generate_connect(connect)?;
4145        }
4146
4147        // GROUP BY
4148        if let Some(group_by) = &select.group_by {
4149            if self.config.pretty {
4150                // Output leading comments on their own lines before GROUP BY
4151                for comment in &group_by.comments {
4152                    self.write_newline();
4153                    self.write_indent();
4154                    self.write_formatted_comment(comment);
4155                }
4156                self.write_newline();
4157                self.write_indent();
4158            } else {
4159                self.write_space();
4160                // In non-pretty mode, output comments inline
4161                for comment in &group_by.comments {
4162                    self.write_formatted_comment(comment);
4163                    self.write_space();
4164                }
4165            }
4166            self.write_keyword("GROUP BY");
4167            // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
4168            match group_by.all {
4169                Some(true) => {
4170                    self.write_space();
4171                    self.write_keyword("ALL");
4172                }
4173                Some(false) => {
4174                    self.write_space();
4175                    self.write_keyword("DISTINCT");
4176                }
4177                None => {}
4178            }
4179            if !group_by.expressions.is_empty() {
4180                // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
4181                // These are represented as Cube/Rollup expressions with empty expressions at the end
4182                let mut trailing_cube = false;
4183                let mut trailing_rollup = false;
4184                let mut plain_expressions: Vec<&Expression> = Vec::new();
4185                let mut grouping_sets_expressions: Vec<&Expression> = Vec::new();
4186                let mut cube_expressions: Vec<&Expression> = Vec::new();
4187                let mut rollup_expressions: Vec<&Expression> = Vec::new();
4188
4189                for expr in &group_by.expressions {
4190                    match expr {
4191                        Expression::Cube(c) if c.expressions.is_empty() => {
4192                            trailing_cube = true;
4193                        }
4194                        Expression::Rollup(r) if r.expressions.is_empty() => {
4195                            trailing_rollup = true;
4196                        }
4197                        Expression::Function(f) if f.name == "CUBE" => {
4198                            cube_expressions.push(expr);
4199                        }
4200                        Expression::Function(f) if f.name == "ROLLUP" => {
4201                            rollup_expressions.push(expr);
4202                        }
4203                        Expression::Function(f) if f.name == "GROUPING SETS" => {
4204                            grouping_sets_expressions.push(expr);
4205                        }
4206                        _ => {
4207                            plain_expressions.push(expr);
4208                        }
4209                    }
4210                }
4211
4212                // Reorder: plain expressions first, then GROUPING SETS, CUBE, ROLLUP
4213                let mut regular_expressions: Vec<&Expression> = Vec::new();
4214                regular_expressions.extend(plain_expressions);
4215                regular_expressions.extend(grouping_sets_expressions);
4216                regular_expressions.extend(cube_expressions);
4217                regular_expressions.extend(rollup_expressions);
4218
4219                if self.config.pretty {
4220                    self.write_newline();
4221                    self.indent_level += 1;
4222                    self.write_indent();
4223                } else {
4224                    self.write_space();
4225                }
4226
4227                for (i, expr) in regular_expressions.iter().enumerate() {
4228                    if i > 0 {
4229                        if self.config.pretty {
4230                            self.write(",");
4231                            self.write_newline();
4232                            self.write_indent();
4233                        } else {
4234                            self.write(", ");
4235                        }
4236                    }
4237                    self.generate_expression(expr)?;
4238                }
4239
4240                if self.config.pretty {
4241                    self.indent_level -= 1;
4242                }
4243
4244                // Output trailing WITH CUBE or WITH ROLLUP
4245                if trailing_cube {
4246                    self.write_space();
4247                    self.write_keyword("WITH CUBE");
4248                } else if trailing_rollup {
4249                    self.write_space();
4250                    self.write_keyword("WITH ROLLUP");
4251                }
4252            }
4253
4254            // ClickHouse: WITH TOTALS
4255            if group_by.totals {
4256                self.write_space();
4257                self.write_keyword("WITH TOTALS");
4258            }
4259        }
4260
4261        // HAVING
4262        if let Some(having) = &select.having {
4263            if self.config.pretty {
4264                // Output leading comments on their own lines before HAVING
4265                for comment in &having.comments {
4266                    self.write_newline();
4267                    self.write_indent();
4268                    self.write_formatted_comment(comment);
4269                }
4270            } else {
4271                for comment in &having.comments {
4272                    self.write_space();
4273                    self.write_formatted_comment(comment);
4274                }
4275            }
4276            self.write_clause_condition("HAVING", &having.this)?;
4277        }
4278
4279        // QUALIFY and WINDOW clause ordering depends on input SQL
4280        if select.qualify_after_window {
4281            // WINDOW before QUALIFY (DuckDB style)
4282            if let Some(windows) = &select.windows {
4283                self.write_window_clause(windows)?;
4284            }
4285            if let Some(qualify) = &select.qualify {
4286                self.write_clause_condition("QUALIFY", &qualify.this)?;
4287            }
4288        } else {
4289            // QUALIFY before WINDOW (Snowflake/BigQuery default)
4290            if let Some(qualify) = &select.qualify {
4291                self.write_clause_condition("QUALIFY", &qualify.this)?;
4292            }
4293            if let Some(windows) = &select.windows {
4294                self.write_window_clause(windows)?;
4295            }
4296        }
4297
4298        // DISTRIBUTE BY (Hive/Spark)
4299        if let Some(distribute_by) = &select.distribute_by {
4300            self.write_clause_expressions("DISTRIBUTE BY", &distribute_by.expressions)?;
4301        }
4302
4303        // CLUSTER BY (Hive/Spark)
4304        if let Some(cluster_by) = &select.cluster_by {
4305            self.write_order_clause("CLUSTER BY", &cluster_by.expressions)?;
4306        }
4307
4308        // SORT BY (Hive/Spark - comes before ORDER BY)
4309        if let Some(sort_by) = &select.sort_by {
4310            self.write_order_clause("SORT BY", &sort_by.expressions)?;
4311        }
4312
4313        // ORDER BY (or ORDER SIBLINGS BY for Oracle hierarchical queries)
4314        if let Some(order_by) = &select.order_by {
4315            if self.config.pretty {
4316                // Output leading comments on their own lines before ORDER BY
4317                for comment in &order_by.comments {
4318                    self.write_newline();
4319                    self.write_indent();
4320                    self.write_formatted_comment(comment);
4321                }
4322            } else {
4323                for comment in &order_by.comments {
4324                    self.write_space();
4325                    self.write_formatted_comment(comment);
4326                }
4327            }
4328            let keyword = if order_by.siblings {
4329                "ORDER SIBLINGS BY"
4330            } else {
4331                "ORDER BY"
4332            };
4333            self.write_order_clause(keyword, &order_by.expressions)?;
4334        }
4335
4336        // TSQL: FETCH requires ORDER BY. If there's a FETCH but no ORDER BY, add ORDER BY (SELECT NULL) OFFSET 0 ROWS
4337        if select.order_by.is_none()
4338            && select.fetch.is_some()
4339            && matches!(
4340                self.config.dialect,
4341                Some(DialectType::TSQL) | Some(DialectType::Fabric)
4342            )
4343        {
4344            if self.config.pretty {
4345                self.write_newline();
4346                self.write_indent();
4347            } else {
4348                self.write_space();
4349            }
4350            self.write_keyword("ORDER BY (SELECT NULL) OFFSET 0 ROWS");
4351        }
4352
4353        // LIMIT and OFFSET
4354        // PostgreSQL and others use: LIMIT count OFFSET offset
4355        // SQL Server uses: OFFSET ... FETCH (no LIMIT)
4356        // Presto/Trino uses: OFFSET n LIMIT m (offset before limit)
4357        let is_presto_like = matches!(
4358            self.config.dialect,
4359            Some(DialectType::Presto) | Some(DialectType::Trino)
4360        );
4361
4362        if is_presto_like && select.offset.is_some() {
4363            // Presto/Trino syntax: OFFSET n LIMIT m (offset comes first)
4364            if let Some(offset) = &select.offset {
4365                if self.config.pretty {
4366                    self.write_newline();
4367                    self.write_indent();
4368                } else {
4369                    self.write_space();
4370                }
4371                self.write_keyword("OFFSET");
4372                self.write_space();
4373                self.write_limit_expr(&offset.this)?;
4374                if offset.rows == Some(true) {
4375                    self.write_space();
4376                    self.write_keyword("ROWS");
4377                }
4378            }
4379            if let Some(limit) = &select.limit {
4380                if self.config.pretty {
4381                    self.write_newline();
4382                    self.write_indent();
4383                } else {
4384                    self.write_space();
4385                }
4386                self.write_keyword("LIMIT");
4387                self.write_space();
4388                self.write_limit_expr(&limit.this)?;
4389                if limit.percent {
4390                    self.write_space();
4391                    self.write_keyword("PERCENT");
4392                }
4393                // Emit any comments that were captured from before the LIMIT keyword
4394                for comment in &limit.comments {
4395                    self.write(" ");
4396                    self.write_formatted_comment(comment);
4397                }
4398            }
4399        } else {
4400            // Check if FETCH will be converted to LIMIT (used for ordering)
4401            let fetch_as_limit = select.fetch.as_ref().map_or(false, |fetch| {
4402                !fetch.percent
4403                    && !fetch.with_ties
4404                    && fetch.count.is_some()
4405                    && matches!(
4406                        self.config.dialect,
4407                        Some(DialectType::Spark)
4408                            | Some(DialectType::Hive)
4409                            | Some(DialectType::DuckDB)
4410                            | Some(DialectType::SQLite)
4411                            | Some(DialectType::MySQL)
4412                            | Some(DialectType::BigQuery)
4413                            | Some(DialectType::Databricks)
4414                            | Some(DialectType::StarRocks)
4415                            | Some(DialectType::Doris)
4416                            | Some(DialectType::Athena)
4417                            | Some(DialectType::ClickHouse)
4418                            | Some(DialectType::Redshift)
4419                    )
4420            });
4421
4422            // Standard LIMIT clause (skip for SQL Server - we use TOP or OFFSET/FETCH instead)
4423            if let Some(limit) = &select.limit {
4424                // SQL Server uses TOP (no OFFSET) or OFFSET/FETCH (with OFFSET) instead of LIMIT
4425                if !matches!(self.config.dialect, Some(DialectType::TSQL)) {
4426                    if self.config.pretty {
4427                        self.write_newline();
4428                        self.write_indent();
4429                    } else {
4430                        self.write_space();
4431                    }
4432                    self.write_keyword("LIMIT");
4433                    self.write_space();
4434                    self.write_limit_expr(&limit.this)?;
4435                    if limit.percent {
4436                        self.write_space();
4437                        self.write_keyword("PERCENT");
4438                    }
4439                    // Emit any comments that were captured from before the LIMIT keyword
4440                    for comment in &limit.comments {
4441                        self.write(" ");
4442                        self.write_formatted_comment(comment);
4443                    }
4444                }
4445            }
4446
4447            // Convert TOP to LIMIT for non-TOP dialects
4448            if select.top.is_some() && !is_top_dialect && select.limit.is_none() {
4449                if let Some(top) = &select.top {
4450                    if !top.percent && !top.with_ties {
4451                        if self.config.pretty {
4452                            self.write_newline();
4453                            self.write_indent();
4454                        } else {
4455                            self.write_space();
4456                        }
4457                        self.write_keyword("LIMIT");
4458                        self.write_space();
4459                        self.generate_expression(&top.this)?;
4460                    }
4461                }
4462            }
4463
4464            // If FETCH will be converted to LIMIT and there's also OFFSET,
4465            // emit LIMIT from FETCH BEFORE the OFFSET
4466            if fetch_as_limit && select.offset.is_some() {
4467                if let Some(fetch) = &select.fetch {
4468                    if self.config.pretty {
4469                        self.write_newline();
4470                        self.write_indent();
4471                    } else {
4472                        self.write_space();
4473                    }
4474                    self.write_keyword("LIMIT");
4475                    self.write_space();
4476                    self.generate_expression(fetch.count.as_ref().unwrap())?;
4477                }
4478            }
4479
4480            // OFFSET
4481            // In SQL Server, OFFSET requires ORDER BY and uses different syntax
4482            // OFFSET x ROWS FETCH NEXT y ROWS ONLY
4483            if let Some(offset) = &select.offset {
4484                if self.config.pretty {
4485                    self.write_newline();
4486                    self.write_indent();
4487                } else {
4488                    self.write_space();
4489                }
4490                if matches!(self.config.dialect, Some(DialectType::TSQL)) {
4491                    // SQL Server 2012+ OFFSET ... FETCH syntax
4492                    self.write_keyword("OFFSET");
4493                    self.write_space();
4494                    self.write_limit_expr(&offset.this)?;
4495                    self.write_space();
4496                    self.write_keyword("ROWS");
4497                    // If there was a LIMIT, use FETCH NEXT ... ROWS ONLY
4498                    if let Some(limit) = &select.limit {
4499                        self.write_space();
4500                        self.write_keyword("FETCH NEXT");
4501                        self.write_space();
4502                        self.write_limit_expr(&limit.this)?;
4503                        self.write_space();
4504                        self.write_keyword("ROWS ONLY");
4505                    }
4506                } else {
4507                    self.write_keyword("OFFSET");
4508                    self.write_space();
4509                    self.write_limit_expr(&offset.this)?;
4510                    // Output ROWS keyword if it was in the original SQL
4511                    if offset.rows == Some(true) {
4512                        self.write_space();
4513                        self.write_keyword("ROWS");
4514                    }
4515                }
4516            }
4517        }
4518
4519        // ClickHouse LIMIT BY clause (after LIMIT/OFFSET)
4520        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
4521            if let Some(limit_by) = &select.limit_by {
4522                if !limit_by.is_empty() {
4523                    self.write_space();
4524                    self.write_keyword("BY");
4525                    self.write_space();
4526                    for (i, expr) in limit_by.iter().enumerate() {
4527                        if i > 0 {
4528                            self.write(", ");
4529                        }
4530                        self.generate_expression(expr)?;
4531                    }
4532                }
4533            }
4534        }
4535
4536        // ClickHouse SETTINGS and FORMAT modifiers (after LIMIT/OFFSET)
4537        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
4538            if let Some(settings) = &select.settings {
4539                if self.config.pretty {
4540                    self.write_newline();
4541                    self.write_indent();
4542                } else {
4543                    self.write_space();
4544                }
4545                self.write_keyword("SETTINGS");
4546                self.write_space();
4547                for (i, expr) in settings.iter().enumerate() {
4548                    if i > 0 {
4549                        self.write(", ");
4550                    }
4551                    self.generate_expression(expr)?;
4552                }
4553            }
4554
4555            if let Some(format_expr) = &select.format {
4556                if self.config.pretty {
4557                    self.write_newline();
4558                    self.write_indent();
4559                } else {
4560                    self.write_space();
4561                }
4562                self.write_keyword("FORMAT");
4563                self.write_space();
4564                self.generate_expression(format_expr)?;
4565            }
4566        }
4567
4568        // FETCH FIRST/NEXT
4569        if let Some(fetch) = &select.fetch {
4570            // Check if we already emitted LIMIT from FETCH before OFFSET
4571            let fetch_already_as_limit = select.offset.is_some()
4572                && !fetch.percent
4573                && !fetch.with_ties
4574                && fetch.count.is_some()
4575                && matches!(
4576                    self.config.dialect,
4577                    Some(DialectType::Spark)
4578                        | Some(DialectType::Hive)
4579                        | Some(DialectType::DuckDB)
4580                        | Some(DialectType::SQLite)
4581                        | Some(DialectType::MySQL)
4582                        | Some(DialectType::BigQuery)
4583                        | Some(DialectType::Databricks)
4584                        | Some(DialectType::StarRocks)
4585                        | Some(DialectType::Doris)
4586                        | Some(DialectType::Athena)
4587                        | Some(DialectType::ClickHouse)
4588                        | Some(DialectType::Redshift)
4589                );
4590
4591            if fetch_already_as_limit {
4592                // Already emitted as LIMIT before OFFSET, skip
4593            } else {
4594                if self.config.pretty {
4595                    self.write_newline();
4596                    self.write_indent();
4597                } else {
4598                    self.write_space();
4599                }
4600
4601                // Convert FETCH to LIMIT for dialects that prefer LIMIT syntax
4602                let use_limit = !fetch.percent
4603                    && !fetch.with_ties
4604                    && fetch.count.is_some()
4605                    && matches!(
4606                        self.config.dialect,
4607                        Some(DialectType::Spark)
4608                            | Some(DialectType::Hive)
4609                            | Some(DialectType::DuckDB)
4610                            | Some(DialectType::SQLite)
4611                            | Some(DialectType::MySQL)
4612                            | Some(DialectType::BigQuery)
4613                            | Some(DialectType::Databricks)
4614                            | Some(DialectType::StarRocks)
4615                            | Some(DialectType::Doris)
4616                            | Some(DialectType::Athena)
4617                            | Some(DialectType::ClickHouse)
4618                            | Some(DialectType::Redshift)
4619                    );
4620
4621                if use_limit {
4622                    self.write_keyword("LIMIT");
4623                    self.write_space();
4624                    self.generate_expression(fetch.count.as_ref().unwrap())?;
4625                } else {
4626                    self.write_keyword("FETCH");
4627                    self.write_space();
4628                    self.write_keyword(&fetch.direction);
4629                    if let Some(ref count) = fetch.count {
4630                        self.write_space();
4631                        self.generate_expression(count)?;
4632                    }
4633                    if fetch.percent {
4634                        self.write_space();
4635                        self.write_keyword("PERCENT");
4636                    }
4637                    if fetch.rows {
4638                        self.write_space();
4639                        self.write_keyword("ROWS");
4640                    }
4641                    if fetch.with_ties {
4642                        self.write_space();
4643                        self.write_keyword("WITH TIES");
4644                    } else {
4645                        self.write_space();
4646                        self.write_keyword("ONLY");
4647                    }
4648                }
4649            } // close fetch_already_as_limit else
4650        }
4651
4652        // SAMPLE / TABLESAMPLE
4653        if let Some(sample) = &select.sample {
4654            use crate::dialects::DialectType;
4655            if self.config.pretty {
4656                self.write_newline();
4657            } else {
4658                self.write_space();
4659            }
4660
4661            if sample.is_using_sample {
4662                // DuckDB USING SAMPLE: METHOD (size UNIT) [REPEATABLE (seed)]
4663                self.write_keyword("USING SAMPLE");
4664                self.generate_sample_body(sample)?;
4665            } else {
4666                self.write_keyword("TABLESAMPLE");
4667
4668                // Snowflake defaults to BERNOULLI when no explicit method is given
4669                let snowflake_bernoulli =
4670                    matches!(self.config.dialect, Some(DialectType::Snowflake))
4671                        && !sample.explicit_method;
4672                if snowflake_bernoulli {
4673                    self.write_space();
4674                    self.write_keyword("BERNOULLI");
4675                }
4676
4677                // Handle BUCKET sampling: TABLESAMPLE (BUCKET 1 OUT OF 5 ON x)
4678                if matches!(sample.method, SampleMethod::Bucket) {
4679                    self.write_space();
4680                    self.write("(");
4681                    self.write_keyword("BUCKET");
4682                    self.write_space();
4683                    if let Some(ref num) = sample.bucket_numerator {
4684                        self.generate_expression(num)?;
4685                    }
4686                    self.write_space();
4687                    self.write_keyword("OUT OF");
4688                    self.write_space();
4689                    if let Some(ref denom) = sample.bucket_denominator {
4690                        self.generate_expression(denom)?;
4691                    }
4692                    if let Some(ref field) = sample.bucket_field {
4693                        self.write_space();
4694                        self.write_keyword("ON");
4695                        self.write_space();
4696                        self.generate_expression(field)?;
4697                    }
4698                    self.write(")");
4699                } else if sample.unit_after_size {
4700                    // Syntax: TABLESAMPLE [METHOD] (size ROWS) or TABLESAMPLE [METHOD] (size PERCENT)
4701                    if sample.explicit_method && sample.method_before_size {
4702                        self.write_space();
4703                        match sample.method {
4704                            SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
4705                            SampleMethod::System => self.write_keyword("SYSTEM"),
4706                            SampleMethod::Block => self.write_keyword("BLOCK"),
4707                            SampleMethod::Row => self.write_keyword("ROW"),
4708                            SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
4709                            _ => {}
4710                        }
4711                    }
4712                    self.write(" (");
4713                    self.generate_expression(&sample.size)?;
4714                    self.write_space();
4715                    match sample.method {
4716                        SampleMethod::Percent => self.write_keyword("PERCENT"),
4717                        SampleMethod::Row => self.write_keyword("ROWS"),
4718                        SampleMethod::Reservoir => self.write_keyword("ROWS"),
4719                        _ => {
4720                            self.write_keyword("PERCENT");
4721                        }
4722                    }
4723                    self.write(")");
4724                } else {
4725                    // Syntax: TABLESAMPLE METHOD (size)
4726                    self.write_space();
4727                    match sample.method {
4728                        SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
4729                        SampleMethod::System => self.write_keyword("SYSTEM"),
4730                        SampleMethod::Block => self.write_keyword("BLOCK"),
4731                        SampleMethod::Row => self.write_keyword("ROW"),
4732                        SampleMethod::Percent => self.write_keyword("BERNOULLI"),
4733                        SampleMethod::Bucket => {}
4734                        SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
4735                    }
4736                    self.write(" (");
4737                    self.generate_expression(&sample.size)?;
4738                    if matches!(sample.method, SampleMethod::Percent) {
4739                        self.write_space();
4740                        self.write_keyword("PERCENT");
4741                    }
4742                    self.write(")");
4743                }
4744            }
4745
4746            if let Some(seed) = &sample.seed {
4747                self.write_space();
4748                // Databricks/Spark use REPEATABLE, not SEED
4749                let use_seed = sample.use_seed_keyword
4750                    && !matches!(
4751                        self.config.dialect,
4752                        Some(crate::dialects::DialectType::Databricks)
4753                            | Some(crate::dialects::DialectType::Spark)
4754                    );
4755                if use_seed {
4756                    self.write_keyword("SEED");
4757                } else {
4758                    self.write_keyword("REPEATABLE");
4759                }
4760                self.write(" (");
4761                self.generate_expression(seed)?;
4762                self.write(")");
4763            }
4764        }
4765
4766        // FOR UPDATE/SHARE locks
4767        // Skip locking clauses for dialects that don't support them
4768        if self.config.locking_reads_supported {
4769            for lock in &select.locks {
4770                if self.config.pretty {
4771                    self.write_newline();
4772                    self.write_indent();
4773                } else {
4774                    self.write_space();
4775                }
4776                self.generate_lock(lock)?;
4777            }
4778        }
4779
4780        // FOR XML clause (T-SQL)
4781        if !select.for_xml.is_empty() {
4782            if self.config.pretty {
4783                self.write_newline();
4784                self.write_indent();
4785            } else {
4786                self.write_space();
4787            }
4788            self.write_keyword("FOR XML");
4789            for (i, opt) in select.for_xml.iter().enumerate() {
4790                if self.config.pretty {
4791                    if i > 0 {
4792                        self.write(",");
4793                    }
4794                    self.write_newline();
4795                    self.write_indent();
4796                    self.write("  "); // extra indent for options
4797                } else {
4798                    if i > 0 {
4799                        self.write(",");
4800                    }
4801                    self.write_space();
4802                }
4803                self.generate_for_xml_option(opt)?;
4804            }
4805        }
4806
4807        // TSQL: OPTION clause
4808        if let Some(ref option) = select.option {
4809            if matches!(
4810                self.config.dialect,
4811                Some(crate::dialects::DialectType::TSQL)
4812                    | Some(crate::dialects::DialectType::Fabric)
4813            ) {
4814                self.write_space();
4815                self.write(option);
4816            }
4817        }
4818
4819        Ok(())
4820    }
4821
4822    /// Generate a single FOR XML option
4823    fn generate_for_xml_option(&mut self, opt: &Expression) -> Result<()> {
4824        match opt {
4825            Expression::QueryOption(qo) => {
4826                // Extract the option name from Var
4827                if let Expression::Var(var) = &*qo.this {
4828                    self.write(&var.this);
4829                } else {
4830                    self.generate_expression(&qo.this)?;
4831                }
4832                // If there's an expression (like PATH('element')), output it in parens
4833                if let Some(expr) = &qo.expression {
4834                    self.write("(");
4835                    self.generate_expression(expr)?;
4836                    self.write(")");
4837                }
4838            }
4839            _ => {
4840                self.generate_expression(opt)?;
4841            }
4842        }
4843        Ok(())
4844    }
4845
4846    fn generate_with(&mut self, with: &With) -> Result<()> {
4847        use crate::dialects::DialectType;
4848
4849        // Output leading comments before WITH
4850        for comment in &with.leading_comments {
4851            self.write_formatted_comment(comment);
4852            self.write(" ");
4853        }
4854        self.write_keyword("WITH");
4855        if with.recursive && self.config.cte_recursive_keyword_required {
4856            self.write_space();
4857            self.write_keyword("RECURSIVE");
4858        }
4859        self.write_space();
4860
4861        // BigQuery doesn't support column aliases in CTE definitions
4862        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
4863
4864        for (i, cte) in with.ctes.iter().enumerate() {
4865            if i > 0 {
4866                self.write(",");
4867                if self.config.pretty {
4868                    self.write_space();
4869                } else {
4870                    self.write(" ");
4871                }
4872            }
4873            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !cte.alias_first {
4874                self.generate_expression(&cte.this)?;
4875                self.write_space();
4876                self.write_keyword("AS");
4877                self.write_space();
4878                self.generate_identifier(&cte.alias)?;
4879                continue;
4880            }
4881            self.generate_identifier(&cte.alias)?;
4882            // Output CTE comments after alias name, before AS
4883            for comment in &cte.comments {
4884                self.write_space();
4885                self.write_formatted_comment(comment);
4886            }
4887            if !cte.columns.is_empty() && !skip_cte_columns {
4888                self.write("(");
4889                for (j, col) in cte.columns.iter().enumerate() {
4890                    if j > 0 {
4891                        self.write(", ");
4892                    }
4893                    self.generate_identifier(col)?;
4894                }
4895                self.write(")");
4896            }
4897            // USING KEY (columns) for DuckDB recursive CTEs
4898            if !cte.key_expressions.is_empty() {
4899                self.write_space();
4900                self.write_keyword("USING KEY");
4901                self.write(" (");
4902                for (i, key) in cte.key_expressions.iter().enumerate() {
4903                    if i > 0 {
4904                        self.write(", ");
4905                    }
4906                    self.generate_identifier(key)?;
4907                }
4908                self.write(")");
4909            }
4910            self.write_space();
4911            self.write_keyword("AS");
4912            // MATERIALIZED / NOT MATERIALIZED
4913            if let Some(materialized) = cte.materialized {
4914                self.write_space();
4915                if materialized {
4916                    self.write_keyword("MATERIALIZED");
4917                } else {
4918                    self.write_keyword("NOT MATERIALIZED");
4919                }
4920            }
4921            self.write(" (");
4922            if self.config.pretty {
4923                self.write_newline();
4924                self.indent_level += 1;
4925                self.write_indent();
4926            }
4927            // For Spark/Databricks, VALUES in a CTE must be wrapped with SELECT * FROM
4928            // e.g., WITH t AS (VALUES ('foo_val') AS t(foo1)) -> WITH t AS (SELECT * FROM VALUES ('foo_val') AS t(foo1))
4929            let wrap_values_in_select = matches!(
4930                self.config.dialect,
4931                Some(DialectType::Spark) | Some(DialectType::Databricks)
4932            ) && matches!(&cte.this, Expression::Values(_));
4933
4934            if wrap_values_in_select {
4935                self.write_keyword("SELECT");
4936                self.write(" * ");
4937                self.write_keyword("FROM");
4938                self.write_space();
4939            }
4940            self.generate_expression(&cte.this)?;
4941            if self.config.pretty {
4942                self.write_newline();
4943                self.indent_level -= 1;
4944                self.write_indent();
4945            }
4946            self.write(")");
4947        }
4948
4949        // Generate SEARCH/CYCLE clause if present
4950        if let Some(search) = &with.search {
4951            self.write_space();
4952            self.generate_expression(search)?;
4953        }
4954
4955        Ok(())
4956    }
4957
4958    /// Generate joins with proper nesting structure for pretty printing.
4959    /// Deferred-condition joins "own" the non-deferred joins that follow them
4960    /// within the same nesting_group.
4961    fn generate_joins_with_nesting(&mut self, joins: &[Join]) -> Result<()> {
4962        let mut i = 0;
4963        while i < joins.len() {
4964            if joins[i].deferred_condition {
4965                let parent_group = joins[i].nesting_group;
4966
4967                // This join owns the following non-deferred joins in the same nesting_group
4968                // First output the join keyword and table (without condition)
4969                self.generate_join_without_condition(&joins[i])?;
4970
4971                // Find the range of child joins: same nesting_group and not deferred
4972                let child_start = i + 1;
4973                let mut child_end = child_start;
4974                while child_end < joins.len()
4975                    && !joins[child_end].deferred_condition
4976                    && joins[child_end].nesting_group == parent_group
4977                {
4978                    child_end += 1;
4979                }
4980
4981                // Output child joins with extra indentation
4982                if child_start < child_end {
4983                    self.indent_level += 1;
4984                    for j in child_start..child_end {
4985                        self.generate_join(&joins[j])?;
4986                    }
4987                    self.indent_level -= 1;
4988                }
4989
4990                // Output the deferred condition at the parent level
4991                self.generate_join_condition(&joins[i])?;
4992
4993                i = child_end;
4994            } else {
4995                // Regular join (no nesting)
4996                self.generate_join(&joins[i])?;
4997                i += 1;
4998            }
4999        }
5000        Ok(())
5001    }
5002
5003    /// Generate a join's keyword and table reference, but not its ON/USING condition.
5004    /// Used for deferred-condition joins where the condition is output after child joins.
5005    fn generate_join_without_condition(&mut self, join: &Join) -> Result<()> {
5006        // Save and temporarily clear the condition to prevent generate_join from outputting it
5007        // We achieve this by creating a modified copy
5008        let mut join_copy = join.clone();
5009        join_copy.on = None;
5010        join_copy.using = Vec::new();
5011        join_copy.deferred_condition = false;
5012        self.generate_join(&join_copy)
5013    }
5014
5015    fn generate_join(&mut self, join: &Join) -> Result<()> {
5016        // Implicit (comma) joins: output as ", table" instead of "CROSS JOIN table"
5017        if join.kind == JoinKind::Implicit {
5018            self.write(",");
5019            if self.config.pretty {
5020                self.write_newline();
5021                self.write_indent();
5022            } else {
5023                self.write_space();
5024            }
5025            self.generate_expression(&join.this)?;
5026            return Ok(());
5027        }
5028
5029        if self.config.pretty {
5030            self.write_newline();
5031            self.write_indent();
5032        } else {
5033            self.write_space();
5034        }
5035
5036        // Helper: format hint suffix (e.g., " LOOP" or "")
5037        // Only include join hints for dialects that support them
5038        let hint_str = if self.config.join_hints {
5039            join.join_hint
5040                .as_ref()
5041                .map(|h| format!(" {}", h))
5042                .unwrap_or_default()
5043        } else {
5044            String::new()
5045        };
5046
5047        let clickhouse_join_keyword =
5048            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
5049                if let Some(hint) = &join.join_hint {
5050                    let mut global = false;
5051                    let mut strictness: Option<&'static str> = None;
5052                    for part in hint.split_whitespace() {
5053                        match part.to_uppercase().as_str() {
5054                            "GLOBAL" => global = true,
5055                            "ANY" => strictness = Some("ANY"),
5056                            "ASOF" => strictness = Some("ASOF"),
5057                            "SEMI" => strictness = Some("SEMI"),
5058                            "ANTI" => strictness = Some("ANTI"),
5059                            _ => {}
5060                        }
5061                    }
5062
5063                    if global || strictness.is_some() {
5064                        let join_type = match join.kind {
5065                            JoinKind::Left => {
5066                                if join.use_outer_keyword {
5067                                    "LEFT OUTER"
5068                                } else if join.use_inner_keyword {
5069                                    "LEFT INNER"
5070                                } else {
5071                                    "LEFT"
5072                                }
5073                            }
5074                            JoinKind::Right => {
5075                                if join.use_outer_keyword {
5076                                    "RIGHT OUTER"
5077                                } else if join.use_inner_keyword {
5078                                    "RIGHT INNER"
5079                                } else {
5080                                    "RIGHT"
5081                                }
5082                            }
5083                            JoinKind::Full => {
5084                                if join.use_outer_keyword {
5085                                    "FULL OUTER"
5086                                } else {
5087                                    "FULL"
5088                                }
5089                            }
5090                            JoinKind::Inner => {
5091                                if join.use_inner_keyword {
5092                                    "INNER"
5093                                } else {
5094                                    ""
5095                                }
5096                            }
5097                            _ => "",
5098                        };
5099
5100                        let mut parts = Vec::new();
5101                        if global {
5102                            parts.push("GLOBAL");
5103                        }
5104                        if !join_type.is_empty() {
5105                            parts.push(join_type);
5106                        }
5107                        if let Some(strict) = strictness {
5108                            parts.push(strict);
5109                        }
5110                        parts.push("JOIN");
5111                        Some(parts.join(" "))
5112                    } else {
5113                        None
5114                    }
5115                } else {
5116                    None
5117                }
5118            } else {
5119                None
5120            };
5121
5122        // Output any comments associated with this join
5123        // In pretty mode, comments go on their own line before the join keyword
5124        // In non-pretty mode, comments go inline before the join keyword
5125        if !join.comments.is_empty() {
5126            if self.config.pretty {
5127                // In pretty mode, go back before the newline+indent we just wrote
5128                // and output comments on their own lines
5129                // We need to output comments BEFORE the join keyword on separate lines
5130                // Trim the trailing newline+indent we already wrote
5131                let trimmed = self.output.trim_end().len();
5132                self.output.truncate(trimmed);
5133                for comment in &join.comments {
5134                    self.write_newline();
5135                    self.write_indent();
5136                    self.write_formatted_comment(comment);
5137                }
5138                self.write_newline();
5139                self.write_indent();
5140            } else {
5141                for comment in &join.comments {
5142                    self.write_formatted_comment(comment);
5143                    self.write_space();
5144                }
5145            }
5146        }
5147
5148        let directed_str = if join.directed { " DIRECTED" } else { "" };
5149
5150        if let Some(keyword) = clickhouse_join_keyword {
5151            self.write_keyword(&keyword);
5152        } else {
5153            match join.kind {
5154                JoinKind::Inner => {
5155                    if join.use_inner_keyword {
5156                        self.write_keyword(&format!("INNER{}{} JOIN", hint_str, directed_str));
5157                    } else {
5158                        self.write_keyword(&format!(
5159                            "{}{}JOIN",
5160                            if hint_str.is_empty() {
5161                                String::new()
5162                            } else {
5163                                format!("{} ", hint_str.trim())
5164                            },
5165                            if directed_str.is_empty() {
5166                                ""
5167                            } else {
5168                                "DIRECTED "
5169                            }
5170                        ));
5171                    }
5172                }
5173                JoinKind::Left => {
5174                    if join.use_outer_keyword {
5175                        self.write_keyword(&format!("LEFT OUTER{}{} JOIN", hint_str, directed_str));
5176                    } else if join.use_inner_keyword {
5177                        self.write_keyword(&format!("LEFT INNER{}{} JOIN", hint_str, directed_str));
5178                    } else {
5179                        self.write_keyword(&format!("LEFT{}{} JOIN", hint_str, directed_str));
5180                    }
5181                }
5182                JoinKind::Right => {
5183                    if join.use_outer_keyword {
5184                        self.write_keyword(&format!(
5185                            "RIGHT OUTER{}{} JOIN",
5186                            hint_str, directed_str
5187                        ));
5188                    } else if join.use_inner_keyword {
5189                        self.write_keyword(&format!(
5190                            "RIGHT INNER{}{} JOIN",
5191                            hint_str, directed_str
5192                        ));
5193                    } else {
5194                        self.write_keyword(&format!("RIGHT{}{} JOIN", hint_str, directed_str));
5195                    }
5196                }
5197                JoinKind::Full => {
5198                    if join.use_outer_keyword {
5199                        self.write_keyword(&format!("FULL OUTER{}{} JOIN", hint_str, directed_str));
5200                    } else {
5201                        self.write_keyword(&format!("FULL{}{} JOIN", hint_str, directed_str));
5202                    }
5203                }
5204                JoinKind::Outer => self.write_keyword(&format!("OUTER{} JOIN", directed_str)),
5205                JoinKind::Cross => self.write_keyword(&format!("CROSS{} JOIN", directed_str)),
5206                JoinKind::Natural => {
5207                    if join.use_inner_keyword {
5208                        self.write_keyword(&format!("NATURAL INNER{} JOIN", directed_str));
5209                    } else {
5210                        self.write_keyword(&format!("NATURAL{} JOIN", directed_str));
5211                    }
5212                }
5213                JoinKind::NaturalLeft => {
5214                    if join.use_outer_keyword {
5215                        self.write_keyword(&format!("NATURAL LEFT OUTER{} JOIN", directed_str));
5216                    } else {
5217                        self.write_keyword(&format!("NATURAL LEFT{} JOIN", directed_str));
5218                    }
5219                }
5220                JoinKind::NaturalRight => {
5221                    if join.use_outer_keyword {
5222                        self.write_keyword(&format!("NATURAL RIGHT OUTER{} JOIN", directed_str));
5223                    } else {
5224                        self.write_keyword(&format!("NATURAL RIGHT{} JOIN", directed_str));
5225                    }
5226                }
5227                JoinKind::NaturalFull => {
5228                    if join.use_outer_keyword {
5229                        self.write_keyword(&format!("NATURAL FULL OUTER{} JOIN", directed_str));
5230                    } else {
5231                        self.write_keyword(&format!("NATURAL FULL{} JOIN", directed_str));
5232                    }
5233                }
5234                JoinKind::Semi => self.write_keyword("SEMI JOIN"),
5235                JoinKind::Anti => self.write_keyword("ANTI JOIN"),
5236                JoinKind::LeftSemi => self.write_keyword("LEFT SEMI JOIN"),
5237                JoinKind::LeftAnti => self.write_keyword("LEFT ANTI JOIN"),
5238                JoinKind::RightSemi => self.write_keyword("RIGHT SEMI JOIN"),
5239                JoinKind::RightAnti => self.write_keyword("RIGHT ANTI JOIN"),
5240                JoinKind::CrossApply => {
5241                    // CROSS APPLY -> INNER JOIN LATERAL for non-TSQL dialects
5242                    if matches!(self.config.dialect, Some(DialectType::TSQL) | None) {
5243                        self.write_keyword("CROSS APPLY");
5244                    } else {
5245                        self.write_keyword("INNER JOIN LATERAL");
5246                    }
5247                }
5248                JoinKind::OuterApply => {
5249                    // OUTER APPLY -> LEFT JOIN LATERAL for non-TSQL dialects
5250                    if matches!(self.config.dialect, Some(DialectType::TSQL) | None) {
5251                        self.write_keyword("OUTER APPLY");
5252                    } else {
5253                        self.write_keyword("LEFT JOIN LATERAL");
5254                    }
5255                }
5256                JoinKind::AsOf => self.write_keyword("ASOF JOIN"),
5257                JoinKind::AsOfLeft => {
5258                    if join.use_outer_keyword {
5259                        self.write_keyword("ASOF LEFT OUTER JOIN");
5260                    } else {
5261                        self.write_keyword("ASOF LEFT JOIN");
5262                    }
5263                }
5264                JoinKind::AsOfRight => {
5265                    if join.use_outer_keyword {
5266                        self.write_keyword("ASOF RIGHT OUTER JOIN");
5267                    } else {
5268                        self.write_keyword("ASOF RIGHT JOIN");
5269                    }
5270                }
5271                JoinKind::Lateral => self.write_keyword("LATERAL JOIN"),
5272                JoinKind::LeftLateral => {
5273                    if join.use_outer_keyword {
5274                        self.write_keyword("LEFT OUTER LATERAL JOIN");
5275                    } else {
5276                        self.write_keyword("LEFT LATERAL JOIN");
5277                    }
5278                }
5279                JoinKind::Straight => self.write_keyword("STRAIGHT_JOIN"),
5280                JoinKind::Implicit => {
5281                    // BigQuery, Hive, Spark, and Databricks prefer explicit CROSS JOIN over comma syntax
5282                    // But only when source is the same dialect (identity) or source is another CROSS JOIN dialect
5283                    // When source is Generic, keep commas (Python sqlglot: parser marks joins, not generator)
5284                    use crate::dialects::DialectType;
5285                    let is_cj_dialect = matches!(
5286                        self.config.dialect,
5287                        Some(DialectType::BigQuery)
5288                            | Some(DialectType::Hive)
5289                            | Some(DialectType::Spark)
5290                            | Some(DialectType::Databricks)
5291                    );
5292                    let source_is_same = self.config.source_dialect.is_some()
5293                        && self.config.source_dialect == self.config.dialect;
5294                    let source_is_cj = matches!(
5295                        self.config.source_dialect,
5296                        Some(DialectType::BigQuery)
5297                            | Some(DialectType::Hive)
5298                            | Some(DialectType::Spark)
5299                            | Some(DialectType::Databricks)
5300                    );
5301                    if is_cj_dialect
5302                        && (source_is_same || source_is_cj || self.config.source_dialect.is_none())
5303                    {
5304                        self.write_keyword("CROSS JOIN");
5305                    } else {
5306                        // Implicit join uses comma: FROM a, b
5307                        // We already wrote a space before the match, so replace with comma
5308                        // by removing trailing space and writing ", "
5309                        self.output.truncate(self.output.trim_end().len());
5310                        self.write(",");
5311                    }
5312                }
5313                JoinKind::Array => self.write_keyword("ARRAY JOIN"),
5314                JoinKind::LeftArray => self.write_keyword("LEFT ARRAY JOIN"),
5315                JoinKind::Paste => self.write_keyword("PASTE JOIN"),
5316            }
5317        }
5318
5319        // ARRAY JOIN items need comma-separated output (Tuple holds multiple items)
5320        if matches!(join.kind, JoinKind::Array | JoinKind::LeftArray) {
5321            self.write_space();
5322            match &join.this {
5323                Expression::Tuple(t) => {
5324                    for (i, item) in t.expressions.iter().enumerate() {
5325                        if i > 0 {
5326                            self.write(", ");
5327                        }
5328                        self.generate_expression(item)?;
5329                    }
5330                }
5331                other => {
5332                    self.generate_expression(other)?;
5333                }
5334            }
5335        } else {
5336            self.write_space();
5337            self.generate_expression(&join.this)?;
5338        }
5339
5340        // Only output MATCH_CONDITION/ON/USING inline if the condition wasn't deferred
5341        if !join.deferred_condition {
5342            // Output MATCH_CONDITION first (Snowflake ASOF JOIN)
5343            if let Some(match_cond) = &join.match_condition {
5344                self.write_space();
5345                self.write_keyword("MATCH_CONDITION");
5346                self.write(" (");
5347                self.generate_expression(match_cond)?;
5348                self.write(")");
5349            }
5350
5351            if let Some(on) = &join.on {
5352                if self.config.pretty {
5353                    self.write_newline();
5354                    self.indent_level += 1;
5355                    self.write_indent();
5356                    self.write_keyword("ON");
5357                    self.write_space();
5358                    self.generate_join_on_condition(on)?;
5359                    self.indent_level -= 1;
5360                } else {
5361                    self.write_space();
5362                    self.write_keyword("ON");
5363                    self.write_space();
5364                    self.generate_expression(on)?;
5365                }
5366            }
5367
5368            if !join.using.is_empty() {
5369                if self.config.pretty {
5370                    self.write_newline();
5371                    self.indent_level += 1;
5372                    self.write_indent();
5373                    self.write_keyword("USING");
5374                    self.write(" (");
5375                    for (i, col) in join.using.iter().enumerate() {
5376                        if i > 0 {
5377                            self.write(", ");
5378                        }
5379                        self.generate_identifier(col)?;
5380                    }
5381                    self.write(")");
5382                    self.indent_level -= 1;
5383                } else {
5384                    self.write_space();
5385                    self.write_keyword("USING");
5386                    self.write(" (");
5387                    for (i, col) in join.using.iter().enumerate() {
5388                        if i > 0 {
5389                            self.write(", ");
5390                        }
5391                        self.generate_identifier(col)?;
5392                    }
5393                    self.write(")");
5394                }
5395            }
5396        }
5397
5398        // Generate PIVOT/UNPIVOT expressions that follow this join
5399        for pivot in &join.pivots {
5400            self.write_space();
5401            self.generate_expression(pivot)?;
5402        }
5403
5404        Ok(())
5405    }
5406
5407    /// Generate just the ON/USING/MATCH_CONDITION for a join (used for deferred conditions)
5408    fn generate_join_condition(&mut self, join: &Join) -> Result<()> {
5409        // Generate MATCH_CONDITION first (Snowflake ASOF JOIN)
5410        if let Some(match_cond) = &join.match_condition {
5411            self.write_space();
5412            self.write_keyword("MATCH_CONDITION");
5413            self.write(" (");
5414            self.generate_expression(match_cond)?;
5415            self.write(")");
5416        }
5417
5418        if let Some(on) = &join.on {
5419            if self.config.pretty {
5420                self.write_newline();
5421                self.indent_level += 1;
5422                self.write_indent();
5423                self.write_keyword("ON");
5424                self.write_space();
5425                // In pretty mode, split AND conditions onto separate lines
5426                self.generate_join_on_condition(on)?;
5427                self.indent_level -= 1;
5428            } else {
5429                self.write_space();
5430                self.write_keyword("ON");
5431                self.write_space();
5432                self.generate_expression(on)?;
5433            }
5434        }
5435
5436        if !join.using.is_empty() {
5437            if self.config.pretty {
5438                self.write_newline();
5439                self.indent_level += 1;
5440                self.write_indent();
5441                self.write_keyword("USING");
5442                self.write(" (");
5443                for (i, col) in join.using.iter().enumerate() {
5444                    if i > 0 {
5445                        self.write(", ");
5446                    }
5447                    self.generate_identifier(col)?;
5448                }
5449                self.write(")");
5450                self.indent_level -= 1;
5451            } else {
5452                self.write_space();
5453                self.write_keyword("USING");
5454                self.write(" (");
5455                for (i, col) in join.using.iter().enumerate() {
5456                    if i > 0 {
5457                        self.write(", ");
5458                    }
5459                    self.generate_identifier(col)?;
5460                }
5461                self.write(")");
5462            }
5463        }
5464
5465        // Generate PIVOT/UNPIVOT expressions that follow this join (for deferred conditions)
5466        for pivot in &join.pivots {
5467            self.write_space();
5468            self.generate_expression(pivot)?;
5469        }
5470
5471        Ok(())
5472    }
5473
5474    /// Generate JOIN ON condition with AND clauses on separate lines in pretty mode
5475    fn generate_join_on_condition(&mut self, expr: &Expression) -> Result<()> {
5476        // If it's an AND expression, split each condition onto a new line
5477        if let Expression::And(and_op) = expr {
5478            // Generate left side (might be another AND)
5479            self.generate_join_on_condition(&and_op.left)?;
5480            // AND on new line
5481            self.write_newline();
5482            self.write_indent();
5483            self.write_keyword("AND");
5484            self.write_space();
5485            // Generate right side (should be a single condition)
5486            self.generate_expression(&and_op.right)?;
5487        } else {
5488            // Base case: single condition
5489            self.generate_expression(expr)?;
5490        }
5491        Ok(())
5492    }
5493
5494    fn generate_joined_table(&mut self, jt: &JoinedTable) -> Result<()> {
5495        // Parenthesized join: (tbl1 CROSS JOIN tbl2)
5496        self.write("(");
5497        self.generate_expression(&jt.left)?;
5498
5499        // Generate all joins
5500        for join in &jt.joins {
5501            self.generate_join(join)?;
5502        }
5503
5504        // Generate LATERAL VIEW clauses (Hive/Spark)
5505        for lv in &jt.lateral_views {
5506            self.generate_lateral_view(lv)?;
5507        }
5508
5509        self.write(")");
5510
5511        // Alias
5512        if let Some(alias) = &jt.alias {
5513            self.write_space();
5514            self.write_keyword("AS");
5515            self.write_space();
5516            self.generate_identifier(alias)?;
5517        }
5518
5519        Ok(())
5520    }
5521
5522    fn generate_lateral_view(&mut self, lv: &LateralView) -> Result<()> {
5523        use crate::dialects::DialectType;
5524
5525        if self.config.pretty {
5526            self.write_newline();
5527            self.write_indent();
5528        } else {
5529            self.write_space();
5530        }
5531
5532        // For Hive/Spark/Databricks (or no dialect specified), output native LATERAL VIEW syntax
5533        // For PostgreSQL and other specific dialects, convert to CROSS JOIN (LATERAL or UNNEST)
5534        let use_lateral_join = matches!(
5535            self.config.dialect,
5536            Some(DialectType::PostgreSQL)
5537                | Some(DialectType::DuckDB)
5538                | Some(DialectType::Snowflake)
5539                | Some(DialectType::TSQL)
5540                | Some(DialectType::Presto)
5541                | Some(DialectType::Trino)
5542                | Some(DialectType::Athena)
5543        );
5544
5545        // Check if target dialect should use UNNEST instead of EXPLODE
5546        let use_unnest = matches!(
5547            self.config.dialect,
5548            Some(DialectType::DuckDB)
5549                | Some(DialectType::Presto)
5550                | Some(DialectType::Trino)
5551                | Some(DialectType::Athena)
5552        );
5553
5554        // Check if we need POSEXPLODE -> UNNEST WITH ORDINALITY
5555        let (is_posexplode, func_args) = match &lv.this {
5556            Expression::Explode(uf) => {
5557                // Expression::Explode is the dedicated EXPLODE expression type
5558                (false, vec![uf.this.clone()])
5559            }
5560            Expression::Unnest(uf) => {
5561                let mut args = vec![uf.this.clone()];
5562                args.extend(uf.expressions.clone());
5563                (false, args)
5564            }
5565            Expression::Function(func) => {
5566                let name = func.name.to_uppercase();
5567                if name == "POSEXPLODE" || name == "POSEXPLODE_OUTER" {
5568                    (true, func.args.clone())
5569                } else if name == "EXPLODE" || name == "EXPLODE_OUTER" || name == "INLINE" {
5570                    (false, func.args.clone())
5571                } else {
5572                    (false, vec![])
5573                }
5574            }
5575            _ => (false, vec![]),
5576        };
5577
5578        if use_lateral_join {
5579            // Convert to CROSS JOIN for PostgreSQL-like dialects
5580            if lv.outer {
5581                self.write_keyword("LEFT JOIN LATERAL");
5582            } else {
5583                self.write_keyword("CROSS JOIN");
5584            }
5585            self.write_space();
5586
5587            if use_unnest && !func_args.is_empty() {
5588                // Convert EXPLODE(y) -> UNNEST(y), POSEXPLODE(y) -> UNNEST(y)
5589                // For DuckDB, also convert ARRAY(y) -> [y]
5590                let unnest_args = if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
5591                    // DuckDB: ARRAY(y) -> [y]
5592                    func_args
5593                        .iter()
5594                        .map(|a| {
5595                            if let Expression::Function(ref f) = a {
5596                                if f.name.to_uppercase() == "ARRAY" && f.args.len() == 1 {
5597                                    return Expression::ArrayFunc(Box::new(
5598                                        crate::expressions::ArrayConstructor {
5599                                            expressions: f.args.clone(),
5600                                            bracket_notation: true,
5601                                            use_list_keyword: false,
5602                                        },
5603                                    ));
5604                                }
5605                            }
5606                            a.clone()
5607                        })
5608                        .collect::<Vec<_>>()
5609                } else if matches!(
5610                    self.config.dialect,
5611                    Some(DialectType::Presto)
5612                        | Some(DialectType::Trino)
5613                        | Some(DialectType::Athena)
5614                ) {
5615                    // Presto: ARRAY(y) -> ARRAY[y]
5616                    func_args
5617                        .iter()
5618                        .map(|a| {
5619                            if let Expression::Function(ref f) = a {
5620                                if f.name.to_uppercase() == "ARRAY" && f.args.len() >= 1 {
5621                                    return Expression::ArrayFunc(Box::new(
5622                                        crate::expressions::ArrayConstructor {
5623                                            expressions: f.args.clone(),
5624                                            bracket_notation: true,
5625                                            use_list_keyword: false,
5626                                        },
5627                                    ));
5628                                }
5629                            }
5630                            a.clone()
5631                        })
5632                        .collect::<Vec<_>>()
5633                } else {
5634                    func_args
5635                };
5636
5637                // POSEXPLODE -> LATERAL (SELECT pos - 1 AS pos, col FROM UNNEST(y) WITH ORDINALITY AS t(col, pos))
5638                if is_posexplode {
5639                    self.write_keyword("LATERAL");
5640                    self.write(" (");
5641                    self.write_keyword("SELECT");
5642                    self.write_space();
5643
5644                    // Build the outer SELECT list: pos - 1 AS pos, then data columns
5645                    // column_aliases[0] is the position column, rest are data columns
5646                    let pos_alias = if !lv.column_aliases.is_empty() {
5647                        lv.column_aliases[0].clone()
5648                    } else {
5649                        Identifier::new("pos")
5650                    };
5651                    let data_aliases: Vec<Identifier> = if lv.column_aliases.len() > 1 {
5652                        lv.column_aliases[1..].to_vec()
5653                    } else {
5654                        vec![Identifier::new("col")]
5655                    };
5656
5657                    // pos - 1 AS pos
5658                    self.generate_identifier(&pos_alias)?;
5659                    self.write(" - 1");
5660                    self.write_space();
5661                    self.write_keyword("AS");
5662                    self.write_space();
5663                    self.generate_identifier(&pos_alias)?;
5664
5665                    // , col [, key, value ...]
5666                    for data_col in &data_aliases {
5667                        self.write(", ");
5668                        self.generate_identifier(data_col)?;
5669                    }
5670
5671                    self.write_space();
5672                    self.write_keyword("FROM");
5673                    self.write_space();
5674                    self.write_keyword("UNNEST");
5675                    self.write("(");
5676                    for (i, arg) in unnest_args.iter().enumerate() {
5677                        if i > 0 {
5678                            self.write(", ");
5679                        }
5680                        self.generate_expression(arg)?;
5681                    }
5682                    self.write(")");
5683                    self.write_space();
5684                    self.write_keyword("WITH ORDINALITY");
5685                    self.write_space();
5686                    self.write_keyword("AS");
5687                    self.write_space();
5688
5689                    // Inner alias: t(data_cols..., pos) - data columns first, pos last
5690                    let table_alias_ident = lv
5691                        .table_alias
5692                        .clone()
5693                        .unwrap_or_else(|| Identifier::new("t"));
5694                    self.generate_identifier(&table_alias_ident)?;
5695                    self.write("(");
5696                    for (i, data_col) in data_aliases.iter().enumerate() {
5697                        if i > 0 {
5698                            self.write(", ");
5699                        }
5700                        self.generate_identifier(data_col)?;
5701                    }
5702                    self.write(", ");
5703                    self.generate_identifier(&pos_alias)?;
5704                    self.write("))");
5705                } else {
5706                    self.write_keyword("UNNEST");
5707                    self.write("(");
5708                    for (i, arg) in unnest_args.iter().enumerate() {
5709                        if i > 0 {
5710                            self.write(", ");
5711                        }
5712                        self.generate_expression(arg)?;
5713                    }
5714                    self.write(")");
5715
5716                    // Add table and column aliases for non-POSEXPLODE
5717                    if let Some(alias) = &lv.table_alias {
5718                        self.write_space();
5719                        self.write_keyword("AS");
5720                        self.write_space();
5721                        self.generate_identifier(alias)?;
5722                        if !lv.column_aliases.is_empty() {
5723                            self.write("(");
5724                            for (i, col) in lv.column_aliases.iter().enumerate() {
5725                                if i > 0 {
5726                                    self.write(", ");
5727                                }
5728                                self.generate_identifier(col)?;
5729                            }
5730                            self.write(")");
5731                        }
5732                    } else if !lv.column_aliases.is_empty() {
5733                        self.write_space();
5734                        self.write_keyword("AS");
5735                        self.write(" t(");
5736                        for (i, col) in lv.column_aliases.iter().enumerate() {
5737                            if i > 0 {
5738                                self.write(", ");
5739                            }
5740                            self.generate_identifier(col)?;
5741                        }
5742                        self.write(")");
5743                    }
5744                }
5745            } else {
5746                // Not EXPLODE/POSEXPLODE or not using UNNEST, use LATERAL
5747                if !lv.outer {
5748                    self.write_keyword("LATERAL");
5749                    self.write_space();
5750                }
5751                self.generate_expression(&lv.this)?;
5752
5753                // Add table and column aliases
5754                if let Some(alias) = &lv.table_alias {
5755                    self.write_space();
5756                    self.write_keyword("AS");
5757                    self.write_space();
5758                    self.generate_identifier(alias)?;
5759                    if !lv.column_aliases.is_empty() {
5760                        self.write("(");
5761                        for (i, col) in lv.column_aliases.iter().enumerate() {
5762                            if i > 0 {
5763                                self.write(", ");
5764                            }
5765                            self.generate_identifier(col)?;
5766                        }
5767                        self.write(")");
5768                    }
5769                } else if !lv.column_aliases.is_empty() {
5770                    self.write_space();
5771                    self.write_keyword("AS");
5772                    self.write(" t(");
5773                    for (i, col) in lv.column_aliases.iter().enumerate() {
5774                        if i > 0 {
5775                            self.write(", ");
5776                        }
5777                        self.generate_identifier(col)?;
5778                    }
5779                    self.write(")");
5780                }
5781            }
5782
5783            // For LEFT JOIN LATERAL, need ON TRUE
5784            if lv.outer {
5785                self.write_space();
5786                self.write_keyword("ON TRUE");
5787            }
5788        } else {
5789            // Output native LATERAL VIEW syntax (Hive/Spark/Databricks or default)
5790            self.write_keyword("LATERAL VIEW");
5791            if lv.outer {
5792                self.write_space();
5793                self.write_keyword("OUTER");
5794            }
5795            if self.config.pretty {
5796                self.write_newline();
5797                self.write_indent();
5798            } else {
5799                self.write_space();
5800            }
5801            self.generate_expression(&lv.this)?;
5802
5803            // Table alias
5804            if let Some(alias) = &lv.table_alias {
5805                self.write_space();
5806                self.generate_identifier(alias)?;
5807            }
5808
5809            // Column aliases
5810            if !lv.column_aliases.is_empty() {
5811                self.write_space();
5812                self.write_keyword("AS");
5813                self.write_space();
5814                for (i, col) in lv.column_aliases.iter().enumerate() {
5815                    if i > 0 {
5816                        self.write(", ");
5817                    }
5818                    self.generate_identifier(col)?;
5819                }
5820            }
5821        }
5822
5823        Ok(())
5824    }
5825
5826    fn generate_union(&mut self, union: &Union) -> Result<()> {
5827        // WITH clause
5828        if let Some(with) = &union.with {
5829            self.generate_with(with)?;
5830            self.write_space();
5831        }
5832        self.generate_expression(&union.left)?;
5833        if self.config.pretty {
5834            self.write_newline();
5835            self.write_indent();
5836        } else {
5837            self.write_space();
5838        }
5839
5840        // BigQuery set operation modifiers: [side] [kind] UNION
5841        if let Some(side) = &union.side {
5842            self.write_keyword(side);
5843            self.write_space();
5844        }
5845        if let Some(kind) = &union.kind {
5846            self.write_keyword(kind);
5847            self.write_space();
5848        }
5849
5850        self.write_keyword("UNION");
5851        if union.all {
5852            self.write_space();
5853            self.write_keyword("ALL");
5854        } else if union.distinct {
5855            self.write_space();
5856            self.write_keyword("DISTINCT");
5857        }
5858
5859        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
5860        // DuckDB: BY NAME
5861        if union.corresponding || union.by_name {
5862            self.write_space();
5863            self.write_keyword("BY NAME");
5864        }
5865        if !union.on_columns.is_empty() {
5866            self.write_space();
5867            self.write_keyword("ON");
5868            self.write(" (");
5869            for (i, col) in union.on_columns.iter().enumerate() {
5870                if i > 0 {
5871                    self.write(", ");
5872                }
5873                self.generate_expression(col)?;
5874            }
5875            self.write(")");
5876        }
5877
5878        if self.config.pretty {
5879            self.write_newline();
5880            self.write_indent();
5881        } else {
5882            self.write_space();
5883        }
5884        self.generate_expression(&union.right)?;
5885        // ORDER BY, LIMIT, OFFSET for the set operation
5886        if let Some(order_by) = &union.order_by {
5887            if self.config.pretty {
5888                self.write_newline();
5889            } else {
5890                self.write_space();
5891            }
5892            self.write_keyword("ORDER BY");
5893            self.write_space();
5894            for (i, ordered) in order_by.expressions.iter().enumerate() {
5895                if i > 0 {
5896                    self.write(", ");
5897                }
5898                self.generate_ordered(ordered)?;
5899            }
5900        }
5901        if let Some(limit) = &union.limit {
5902            if self.config.pretty {
5903                self.write_newline();
5904            } else {
5905                self.write_space();
5906            }
5907            self.write_keyword("LIMIT");
5908            self.write_space();
5909            self.generate_expression(limit)?;
5910        }
5911        if let Some(offset) = &union.offset {
5912            if self.config.pretty {
5913                self.write_newline();
5914            } else {
5915                self.write_space();
5916            }
5917            self.write_keyword("OFFSET");
5918            self.write_space();
5919            self.generate_expression(offset)?;
5920        }
5921        // DISTRIBUTE BY (Hive/Spark)
5922        if let Some(distribute_by) = &union.distribute_by {
5923            self.write_space();
5924            self.write_keyword("DISTRIBUTE BY");
5925            self.write_space();
5926            for (i, expr) in distribute_by.expressions.iter().enumerate() {
5927                if i > 0 {
5928                    self.write(", ");
5929                }
5930                self.generate_expression(expr)?;
5931            }
5932        }
5933        // SORT BY (Hive/Spark)
5934        if let Some(sort_by) = &union.sort_by {
5935            self.write_space();
5936            self.write_keyword("SORT BY");
5937            self.write_space();
5938            for (i, ord) in sort_by.expressions.iter().enumerate() {
5939                if i > 0 {
5940                    self.write(", ");
5941                }
5942                self.generate_ordered(ord)?;
5943            }
5944        }
5945        // CLUSTER BY (Hive/Spark)
5946        if let Some(cluster_by) = &union.cluster_by {
5947            self.write_space();
5948            self.write_keyword("CLUSTER BY");
5949            self.write_space();
5950            for (i, ord) in cluster_by.expressions.iter().enumerate() {
5951                if i > 0 {
5952                    self.write(", ");
5953                }
5954                self.generate_ordered(ord)?;
5955            }
5956        }
5957        Ok(())
5958    }
5959
5960    fn generate_intersect(&mut self, intersect: &Intersect) -> Result<()> {
5961        // WITH clause
5962        if let Some(with) = &intersect.with {
5963            self.generate_with(with)?;
5964            self.write_space();
5965        }
5966        self.generate_expression(&intersect.left)?;
5967        if self.config.pretty {
5968            self.write_newline();
5969            self.write_indent();
5970        } else {
5971            self.write_space();
5972        }
5973
5974        // BigQuery set operation modifiers: [side] [kind] INTERSECT
5975        if let Some(side) = &intersect.side {
5976            self.write_keyword(side);
5977            self.write_space();
5978        }
5979        if let Some(kind) = &intersect.kind {
5980            self.write_keyword(kind);
5981            self.write_space();
5982        }
5983
5984        self.write_keyword("INTERSECT");
5985        if intersect.all {
5986            self.write_space();
5987            self.write_keyword("ALL");
5988        } else if intersect.distinct {
5989            self.write_space();
5990            self.write_keyword("DISTINCT");
5991        }
5992
5993        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
5994        // DuckDB: BY NAME
5995        if intersect.corresponding || intersect.by_name {
5996            self.write_space();
5997            self.write_keyword("BY NAME");
5998        }
5999        if !intersect.on_columns.is_empty() {
6000            self.write_space();
6001            self.write_keyword("ON");
6002            self.write(" (");
6003            for (i, col) in intersect.on_columns.iter().enumerate() {
6004                if i > 0 {
6005                    self.write(", ");
6006                }
6007                self.generate_expression(col)?;
6008            }
6009            self.write(")");
6010        }
6011
6012        if self.config.pretty {
6013            self.write_newline();
6014            self.write_indent();
6015        } else {
6016            self.write_space();
6017        }
6018        self.generate_expression(&intersect.right)?;
6019        // ORDER BY, LIMIT, OFFSET for the set operation
6020        if let Some(order_by) = &intersect.order_by {
6021            if self.config.pretty {
6022                self.write_newline();
6023            } else {
6024                self.write_space();
6025            }
6026            self.write_keyword("ORDER BY");
6027            self.write_space();
6028            for (i, ordered) in order_by.expressions.iter().enumerate() {
6029                if i > 0 {
6030                    self.write(", ");
6031                }
6032                self.generate_ordered(ordered)?;
6033            }
6034        }
6035        if let Some(limit) = &intersect.limit {
6036            if self.config.pretty {
6037                self.write_newline();
6038            } else {
6039                self.write_space();
6040            }
6041            self.write_keyword("LIMIT");
6042            self.write_space();
6043            self.generate_expression(limit)?;
6044        }
6045        if let Some(offset) = &intersect.offset {
6046            if self.config.pretty {
6047                self.write_newline();
6048            } else {
6049                self.write_space();
6050            }
6051            self.write_keyword("OFFSET");
6052            self.write_space();
6053            self.generate_expression(offset)?;
6054        }
6055        // DISTRIBUTE BY (Hive/Spark)
6056        if let Some(distribute_by) = &intersect.distribute_by {
6057            self.write_space();
6058            self.write_keyword("DISTRIBUTE BY");
6059            self.write_space();
6060            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6061                if i > 0 {
6062                    self.write(", ");
6063                }
6064                self.generate_expression(expr)?;
6065            }
6066        }
6067        // SORT BY (Hive/Spark)
6068        if let Some(sort_by) = &intersect.sort_by {
6069            self.write_space();
6070            self.write_keyword("SORT BY");
6071            self.write_space();
6072            for (i, ord) in sort_by.expressions.iter().enumerate() {
6073                if i > 0 {
6074                    self.write(", ");
6075                }
6076                self.generate_ordered(ord)?;
6077            }
6078        }
6079        // CLUSTER BY (Hive/Spark)
6080        if let Some(cluster_by) = &intersect.cluster_by {
6081            self.write_space();
6082            self.write_keyword("CLUSTER BY");
6083            self.write_space();
6084            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6085                if i > 0 {
6086                    self.write(", ");
6087                }
6088                self.generate_ordered(ord)?;
6089            }
6090        }
6091        Ok(())
6092    }
6093
6094    fn generate_except(&mut self, except: &Except) -> Result<()> {
6095        use crate::dialects::DialectType;
6096
6097        // WITH clause
6098        if let Some(with) = &except.with {
6099            self.generate_with(with)?;
6100            self.write_space();
6101        }
6102
6103        self.generate_expression(&except.left)?;
6104        if self.config.pretty {
6105            self.write_newline();
6106            self.write_indent();
6107        } else {
6108            self.write_space();
6109        }
6110
6111        // BigQuery set operation modifiers: [side] [kind] EXCEPT
6112        if let Some(side) = &except.side {
6113            self.write_keyword(side);
6114            self.write_space();
6115        }
6116        if let Some(kind) = &except.kind {
6117            self.write_keyword(kind);
6118            self.write_space();
6119        }
6120
6121        // Oracle uses MINUS instead of EXCEPT (but not for EXCEPT ALL)
6122        match self.config.dialect {
6123            Some(DialectType::Oracle) if !except.all => {
6124                self.write_keyword("MINUS");
6125            }
6126            Some(DialectType::ClickHouse) => {
6127                // ClickHouse: drop ALL from EXCEPT ALL
6128                self.write_keyword("EXCEPT");
6129                if except.distinct {
6130                    self.write_space();
6131                    self.write_keyword("DISTINCT");
6132                }
6133            }
6134            Some(DialectType::BigQuery) => {
6135                // BigQuery: bare EXCEPT defaults to EXCEPT DISTINCT
6136                self.write_keyword("EXCEPT");
6137                if except.all {
6138                    self.write_space();
6139                    self.write_keyword("ALL");
6140                } else {
6141                    self.write_space();
6142                    self.write_keyword("DISTINCT");
6143                }
6144            }
6145            _ => {
6146                self.write_keyword("EXCEPT");
6147                if except.all {
6148                    self.write_space();
6149                    self.write_keyword("ALL");
6150                } else if except.distinct {
6151                    self.write_space();
6152                    self.write_keyword("DISTINCT");
6153                }
6154            }
6155        }
6156
6157        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6158        // DuckDB: BY NAME
6159        if except.corresponding || except.by_name {
6160            self.write_space();
6161            self.write_keyword("BY NAME");
6162        }
6163        if !except.on_columns.is_empty() {
6164            self.write_space();
6165            self.write_keyword("ON");
6166            self.write(" (");
6167            for (i, col) in except.on_columns.iter().enumerate() {
6168                if i > 0 {
6169                    self.write(", ");
6170                }
6171                self.generate_expression(col)?;
6172            }
6173            self.write(")");
6174        }
6175
6176        if self.config.pretty {
6177            self.write_newline();
6178            self.write_indent();
6179        } else {
6180            self.write_space();
6181        }
6182        self.generate_expression(&except.right)?;
6183        // ORDER BY, LIMIT, OFFSET for the set operation
6184        if let Some(order_by) = &except.order_by {
6185            if self.config.pretty {
6186                self.write_newline();
6187            } else {
6188                self.write_space();
6189            }
6190            self.write_keyword("ORDER BY");
6191            self.write_space();
6192            for (i, ordered) in order_by.expressions.iter().enumerate() {
6193                if i > 0 {
6194                    self.write(", ");
6195                }
6196                self.generate_ordered(ordered)?;
6197            }
6198        }
6199        if let Some(limit) = &except.limit {
6200            if self.config.pretty {
6201                self.write_newline();
6202            } else {
6203                self.write_space();
6204            }
6205            self.write_keyword("LIMIT");
6206            self.write_space();
6207            self.generate_expression(limit)?;
6208        }
6209        if let Some(offset) = &except.offset {
6210            if self.config.pretty {
6211                self.write_newline();
6212            } else {
6213                self.write_space();
6214            }
6215            self.write_keyword("OFFSET");
6216            self.write_space();
6217            self.generate_expression(offset)?;
6218        }
6219        // DISTRIBUTE BY (Hive/Spark)
6220        if let Some(distribute_by) = &except.distribute_by {
6221            self.write_space();
6222            self.write_keyword("DISTRIBUTE BY");
6223            self.write_space();
6224            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6225                if i > 0 {
6226                    self.write(", ");
6227                }
6228                self.generate_expression(expr)?;
6229            }
6230        }
6231        // SORT BY (Hive/Spark)
6232        if let Some(sort_by) = &except.sort_by {
6233            self.write_space();
6234            self.write_keyword("SORT BY");
6235            self.write_space();
6236            for (i, ord) in sort_by.expressions.iter().enumerate() {
6237                if i > 0 {
6238                    self.write(", ");
6239                }
6240                self.generate_ordered(ord)?;
6241            }
6242        }
6243        // CLUSTER BY (Hive/Spark)
6244        if let Some(cluster_by) = &except.cluster_by {
6245            self.write_space();
6246            self.write_keyword("CLUSTER BY");
6247            self.write_space();
6248            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6249                if i > 0 {
6250                    self.write(", ");
6251                }
6252                self.generate_ordered(ord)?;
6253            }
6254        }
6255        Ok(())
6256    }
6257
6258    fn generate_insert(&mut self, insert: &Insert) -> Result<()> {
6259        // For TSQL/Fabric/Spark/Hive/Databricks, CTEs must be prepended before INSERT
6260        let prepend_query_cte = if insert.with.is_none() {
6261            use crate::dialects::DialectType;
6262            let should_prepend = matches!(
6263                self.config.dialect,
6264                Some(DialectType::TSQL)
6265                    | Some(DialectType::Fabric)
6266                    | Some(DialectType::Spark)
6267                    | Some(DialectType::Databricks)
6268                    | Some(DialectType::Hive)
6269            );
6270            if should_prepend {
6271                if let Some(Expression::Select(select)) = &insert.query {
6272                    select.with.clone()
6273                } else {
6274                    None
6275                }
6276            } else {
6277                None
6278            }
6279        } else {
6280            None
6281        };
6282
6283        // Output WITH clause if on INSERT (e.g., WITH ... INSERT INTO ...)
6284        if let Some(with) = &insert.with {
6285            self.generate_with(with)?;
6286            self.write_space();
6287        } else if let Some(with) = &prepend_query_cte {
6288            self.generate_with(with)?;
6289            self.write_space();
6290        }
6291
6292        // Output leading comments before INSERT
6293        for comment in &insert.leading_comments {
6294            self.write_formatted_comment(comment);
6295            self.write(" ");
6296        }
6297
6298        // Handle directory insert (INSERT OVERWRITE DIRECTORY)
6299        if let Some(dir) = &insert.directory {
6300            self.write_keyword("INSERT OVERWRITE");
6301            if dir.local {
6302                self.write_space();
6303                self.write_keyword("LOCAL");
6304            }
6305            self.write_space();
6306            self.write_keyword("DIRECTORY");
6307            self.write_space();
6308            self.write("'");
6309            self.write(&dir.path);
6310            self.write("'");
6311
6312            // ROW FORMAT clause
6313            if let Some(row_format) = &dir.row_format {
6314                self.write_space();
6315                self.write_keyword("ROW FORMAT");
6316                if row_format.delimited {
6317                    self.write_space();
6318                    self.write_keyword("DELIMITED");
6319                }
6320                if let Some(val) = &row_format.fields_terminated_by {
6321                    self.write_space();
6322                    self.write_keyword("FIELDS TERMINATED BY");
6323                    self.write_space();
6324                    self.write("'");
6325                    self.write(val);
6326                    self.write("'");
6327                }
6328                if let Some(val) = &row_format.collection_items_terminated_by {
6329                    self.write_space();
6330                    self.write_keyword("COLLECTION ITEMS TERMINATED BY");
6331                    self.write_space();
6332                    self.write("'");
6333                    self.write(val);
6334                    self.write("'");
6335                }
6336                if let Some(val) = &row_format.map_keys_terminated_by {
6337                    self.write_space();
6338                    self.write_keyword("MAP KEYS TERMINATED BY");
6339                    self.write_space();
6340                    self.write("'");
6341                    self.write(val);
6342                    self.write("'");
6343                }
6344                if let Some(val) = &row_format.lines_terminated_by {
6345                    self.write_space();
6346                    self.write_keyword("LINES TERMINATED BY");
6347                    self.write_space();
6348                    self.write("'");
6349                    self.write(val);
6350                    self.write("'");
6351                }
6352                if let Some(val) = &row_format.null_defined_as {
6353                    self.write_space();
6354                    self.write_keyword("NULL DEFINED AS");
6355                    self.write_space();
6356                    self.write("'");
6357                    self.write(val);
6358                    self.write("'");
6359                }
6360            }
6361
6362            // STORED AS clause
6363            if let Some(format) = &dir.stored_as {
6364                self.write_space();
6365                self.write_keyword("STORED AS");
6366                self.write_space();
6367                self.write_keyword(format);
6368            }
6369
6370            // Query (SELECT statement)
6371            if let Some(query) = &insert.query {
6372                self.write_space();
6373                self.generate_expression(query)?;
6374            }
6375
6376            return Ok(());
6377        }
6378
6379        if insert.is_replace {
6380            // MySQL/SQLite REPLACE INTO statement
6381            self.write_keyword("REPLACE INTO");
6382        } else if insert.overwrite {
6383            // Use dialect-specific INSERT OVERWRITE format
6384            self.write_keyword("INSERT");
6385            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
6386            if let Some(ref hint) = insert.hint {
6387                self.generate_hint(hint)?;
6388            }
6389            self.write(&self.config.insert_overwrite.to_uppercase());
6390        } else if let Some(ref action) = insert.conflict_action {
6391            // SQLite conflict action: INSERT OR ABORT|FAIL|IGNORE|REPLACE|ROLLBACK INTO
6392            self.write_keyword("INSERT OR");
6393            self.write_space();
6394            self.write_keyword(action);
6395            self.write_space();
6396            self.write_keyword("INTO");
6397        } else if insert.ignore {
6398            // MySQL INSERT IGNORE syntax
6399            self.write_keyword("INSERT IGNORE INTO");
6400        } else {
6401            self.write_keyword("INSERT");
6402            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
6403            if let Some(ref hint) = insert.hint {
6404                self.generate_hint(hint)?;
6405            }
6406            self.write_space();
6407            self.write_keyword("INTO");
6408        }
6409        // ClickHouse: INSERT INTO FUNCTION func_name(args...)
6410        if let Some(ref func) = insert.function_target {
6411            self.write_space();
6412            self.write_keyword("FUNCTION");
6413            self.write_space();
6414            self.generate_expression(func)?;
6415        } else {
6416            self.write_space();
6417            self.generate_table(&insert.table)?;
6418        }
6419
6420        // Table alias (PostgreSQL: INSERT INTO table AS t(...), Oracle: INSERT INTO table t ...)
6421        if let Some(ref alias) = insert.alias {
6422            self.write_space();
6423            if insert.alias_explicit_as {
6424                self.write_keyword("AS");
6425                self.write_space();
6426            }
6427            self.generate_identifier(alias)?;
6428        }
6429
6430        // IF EXISTS clause (Hive)
6431        if insert.if_exists {
6432            self.write_space();
6433            self.write_keyword("IF EXISTS");
6434        }
6435
6436        // REPLACE WHERE clause (Databricks)
6437        if let Some(ref replace_where) = insert.replace_where {
6438            if self.config.pretty {
6439                self.write_newline();
6440                self.write_indent();
6441            } else {
6442                self.write_space();
6443            }
6444            self.write_keyword("REPLACE WHERE");
6445            self.write_space();
6446            self.generate_expression(replace_where)?;
6447        }
6448
6449        // Generate PARTITION clause if present
6450        if !insert.partition.is_empty() {
6451            self.write_space();
6452            self.write_keyword("PARTITION");
6453            self.write("(");
6454            for (i, (col, val)) in insert.partition.iter().enumerate() {
6455                if i > 0 {
6456                    self.write(", ");
6457                }
6458                self.generate_identifier(col)?;
6459                if let Some(v) = val {
6460                    self.write(" = ");
6461                    self.generate_expression(v)?;
6462                }
6463            }
6464            self.write(")");
6465        }
6466
6467        // ClickHouse: PARTITION BY expr
6468        if let Some(ref partition_by) = insert.partition_by {
6469            self.write_space();
6470            self.write_keyword("PARTITION BY");
6471            self.write_space();
6472            self.generate_expression(partition_by)?;
6473        }
6474
6475        // ClickHouse: SETTINGS key = val, ...
6476        if !insert.settings.is_empty() {
6477            self.write_space();
6478            self.write_keyword("SETTINGS");
6479            self.write_space();
6480            for (i, setting) in insert.settings.iter().enumerate() {
6481                if i > 0 {
6482                    self.write(", ");
6483                }
6484                self.generate_expression(setting)?;
6485            }
6486        }
6487
6488        if !insert.columns.is_empty() {
6489            if insert.alias.is_some() && insert.alias_explicit_as {
6490                // No space when explicit AS alias is present: INSERT INTO table AS t(a, b, c)
6491                self.write("(");
6492            } else {
6493                // Space for implicit alias or no alias: INSERT INTO dest d (i, value)
6494                self.write(" (");
6495            }
6496            for (i, col) in insert.columns.iter().enumerate() {
6497                if i > 0 {
6498                    self.write(", ");
6499                }
6500                self.generate_identifier(col)?;
6501            }
6502            self.write(")");
6503        }
6504
6505        // OUTPUT clause (TSQL)
6506        if let Some(ref output) = insert.output {
6507            self.generate_output_clause(output)?;
6508        }
6509
6510        // BY NAME modifier (DuckDB)
6511        if insert.by_name {
6512            self.write_space();
6513            self.write_keyword("BY NAME");
6514        }
6515
6516        if insert.default_values {
6517            self.write_space();
6518            self.write_keyword("DEFAULT VALUES");
6519        } else if let Some(query) = &insert.query {
6520            if self.config.pretty {
6521                self.write_newline();
6522            } else {
6523                self.write_space();
6524            }
6525            // If we prepended CTEs from nested SELECT (TSQL), strip the WITH from SELECT
6526            if prepend_query_cte.is_some() {
6527                if let Expression::Select(select) = query {
6528                    let mut select_no_with = select.clone();
6529                    select_no_with.with = None;
6530                    self.generate_select(&select_no_with)?;
6531                } else {
6532                    self.generate_expression(query)?;
6533                }
6534            } else {
6535                self.generate_expression(query)?;
6536            }
6537        } else if !insert.values.is_empty() {
6538            if self.config.pretty {
6539                // Pretty printing: VALUES on new line, each tuple indented
6540                self.write_newline();
6541                self.write_keyword("VALUES");
6542                self.write_newline();
6543                self.indent_level += 1;
6544                for (i, row) in insert.values.iter().enumerate() {
6545                    if i > 0 {
6546                        self.write(",");
6547                        self.write_newline();
6548                    }
6549                    self.write_indent();
6550                    self.write("(");
6551                    for (j, val) in row.iter().enumerate() {
6552                        if j > 0 {
6553                            self.write(", ");
6554                        }
6555                        self.generate_expression(val)?;
6556                    }
6557                    self.write(")");
6558                }
6559                self.indent_level -= 1;
6560            } else {
6561                // Non-pretty: single line
6562                self.write_space();
6563                self.write_keyword("VALUES");
6564                for (i, row) in insert.values.iter().enumerate() {
6565                    if i > 0 {
6566                        self.write(",");
6567                    }
6568                    self.write(" (");
6569                    for (j, val) in row.iter().enumerate() {
6570                        if j > 0 {
6571                            self.write(", ");
6572                        }
6573                        self.generate_expression(val)?;
6574                    }
6575                    self.write(")");
6576                }
6577            }
6578        }
6579
6580        // Source table (Hive/Spark): INSERT OVERWRITE TABLE target TABLE source
6581        if let Some(ref source) = insert.source {
6582            self.write_space();
6583            self.write_keyword("TABLE");
6584            self.write_space();
6585            self.generate_expression(source)?;
6586        }
6587
6588        // Source alias (MySQL: VALUES (...) AS new_data)
6589        if let Some(alias) = &insert.source_alias {
6590            self.write_space();
6591            self.write_keyword("AS");
6592            self.write_space();
6593            self.generate_identifier(alias)?;
6594        }
6595
6596        // ON CONFLICT clause (Materialize doesn't support ON CONFLICT)
6597        if let Some(on_conflict) = &insert.on_conflict {
6598            if !matches!(self.config.dialect, Some(DialectType::Materialize)) {
6599                self.write_space();
6600                self.generate_expression(on_conflict)?;
6601            }
6602        }
6603
6604        // RETURNING clause
6605        if !insert.returning.is_empty() {
6606            self.write_space();
6607            self.write_keyword("RETURNING");
6608            self.write_space();
6609            for (i, expr) in insert.returning.iter().enumerate() {
6610                if i > 0 {
6611                    self.write(", ");
6612                }
6613                self.generate_expression(expr)?;
6614            }
6615        }
6616
6617        Ok(())
6618    }
6619
6620    fn generate_update(&mut self, update: &Update) -> Result<()> {
6621        // Output leading comments before UPDATE
6622        for comment in &update.leading_comments {
6623            self.write_formatted_comment(comment);
6624            self.write(" ");
6625        }
6626
6627        // WITH clause (CTEs)
6628        if let Some(ref with) = update.with {
6629            self.generate_with(with)?;
6630            self.write_space();
6631        }
6632
6633        self.write_keyword("UPDATE");
6634        self.write_space();
6635        self.generate_table(&update.table)?;
6636
6637        let mysql_like_update_from = matches!(
6638            self.config.dialect,
6639            Some(DialectType::MySQL) | Some(DialectType::SingleStore)
6640        ) && update.from_clause.is_some();
6641
6642        let mut set_pairs = update.set.clone();
6643
6644        // MySQL-style UPDATE doesn't support FROM after SET. Convert FROM tables to JOIN ... ON TRUE.
6645        let mut pre_set_joins = update.table_joins.clone();
6646        if mysql_like_update_from {
6647            let target_name = update
6648                .table
6649                .alias
6650                .as_ref()
6651                .map(|a| a.name.clone())
6652                .unwrap_or_else(|| update.table.name.name.clone());
6653
6654            for (col, _) in &mut set_pairs {
6655                if !col.name.contains('.') {
6656                    col.name = format!("{}.{}", target_name, col.name);
6657                }
6658            }
6659
6660            if let Some(from_clause) = &update.from_clause {
6661                for table_expr in &from_clause.expressions {
6662                    pre_set_joins.push(crate::expressions::Join {
6663                        this: table_expr.clone(),
6664                        on: Some(Expression::Boolean(crate::expressions::BooleanLiteral {
6665                            value: true,
6666                        })),
6667                        using: Vec::new(),
6668                        kind: crate::expressions::JoinKind::Inner,
6669                        use_inner_keyword: false,
6670                        use_outer_keyword: false,
6671                        deferred_condition: false,
6672                        join_hint: None,
6673                        match_condition: None,
6674                        pivots: Vec::new(),
6675                        comments: Vec::new(),
6676                        nesting_group: 0,
6677                        directed: false,
6678                    });
6679                }
6680            }
6681            for join in &update.from_joins {
6682                let mut join = join.clone();
6683                if join.on.is_none() && join.using.is_empty() {
6684                    join.on = Some(Expression::Boolean(crate::expressions::BooleanLiteral {
6685                        value: true,
6686                    }));
6687                }
6688                pre_set_joins.push(join);
6689            }
6690        }
6691
6692        // Extra tables for multi-table UPDATE (MySQL syntax)
6693        for extra_table in &update.extra_tables {
6694            self.write(", ");
6695            self.generate_table(extra_table)?;
6696        }
6697
6698        // JOINs attached to the table list (MySQL multi-table syntax)
6699        for join in &pre_set_joins {
6700            // generate_join already adds a leading space
6701            self.generate_join(join)?;
6702        }
6703
6704        // Teradata: FROM clause comes before SET
6705        let teradata_from_before_set = matches!(self.config.dialect, Some(DialectType::Teradata));
6706        if teradata_from_before_set && !mysql_like_update_from {
6707            if let Some(ref from_clause) = update.from_clause {
6708                self.write_space();
6709                self.write_keyword("FROM");
6710                self.write_space();
6711                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
6712                    if i > 0 {
6713                        self.write(", ");
6714                    }
6715                    self.generate_expression(table_expr)?;
6716                }
6717            }
6718            for join in &update.from_joins {
6719                self.generate_join(join)?;
6720            }
6721        }
6722
6723        self.write_space();
6724        self.write_keyword("SET");
6725        self.write_space();
6726
6727        for (i, (col, val)) in set_pairs.iter().enumerate() {
6728            if i > 0 {
6729                self.write(", ");
6730            }
6731            self.generate_identifier(col)?;
6732            self.write(" = ");
6733            self.generate_expression(val)?;
6734        }
6735
6736        // OUTPUT clause (TSQL)
6737        if let Some(ref output) = update.output {
6738            self.generate_output_clause(output)?;
6739        }
6740
6741        // FROM clause (after SET for non-Teradata, non-MySQL dialects)
6742        if !mysql_like_update_from && !teradata_from_before_set {
6743            if let Some(ref from_clause) = update.from_clause {
6744                self.write_space();
6745                self.write_keyword("FROM");
6746                self.write_space();
6747                // Generate each table in the FROM clause
6748                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
6749                    if i > 0 {
6750                        self.write(", ");
6751                    }
6752                    self.generate_expression(table_expr)?;
6753                }
6754            }
6755        }
6756
6757        if !mysql_like_update_from && !teradata_from_before_set {
6758            // JOINs after FROM clause (PostgreSQL, Snowflake, SQL Server syntax)
6759            for join in &update.from_joins {
6760                self.generate_join(join)?;
6761            }
6762        }
6763
6764        if let Some(where_clause) = &update.where_clause {
6765            self.write_space();
6766            self.write_keyword("WHERE");
6767            self.write_space();
6768            self.generate_expression(&where_clause.this)?;
6769        }
6770
6771        // RETURNING clause
6772        if !update.returning.is_empty() {
6773            self.write_space();
6774            self.write_keyword("RETURNING");
6775            self.write_space();
6776            for (i, expr) in update.returning.iter().enumerate() {
6777                if i > 0 {
6778                    self.write(", ");
6779                }
6780                self.generate_expression(expr)?;
6781            }
6782        }
6783
6784        // ORDER BY clause (MySQL)
6785        if let Some(ref order_by) = update.order_by {
6786            self.write_space();
6787            self.generate_order_by(order_by)?;
6788        }
6789
6790        // LIMIT clause (MySQL)
6791        if let Some(ref limit) = update.limit {
6792            self.write_space();
6793            self.write_keyword("LIMIT");
6794            self.write_space();
6795            self.generate_expression(limit)?;
6796        }
6797
6798        Ok(())
6799    }
6800
6801    fn generate_delete(&mut self, delete: &Delete) -> Result<()> {
6802        // Output WITH clause if present
6803        if let Some(with) = &delete.with {
6804            self.generate_with(with)?;
6805            self.write_space();
6806        }
6807
6808        // Output leading comments before DELETE
6809        for comment in &delete.leading_comments {
6810            self.write_formatted_comment(comment);
6811            self.write(" ");
6812        }
6813
6814        // MySQL multi-table DELETE or TSQL DELETE with OUTPUT before FROM
6815        if !delete.tables.is_empty() && !delete.tables_from_using {
6816            // DELETE t1[, t2] [OUTPUT ...] FROM ... syntax (tables before FROM)
6817            self.write_keyword("DELETE");
6818            self.write_space();
6819            for (i, tbl) in delete.tables.iter().enumerate() {
6820                if i > 0 {
6821                    self.write(", ");
6822                }
6823                self.generate_table(tbl)?;
6824            }
6825            // TSQL: OUTPUT clause between target table and FROM
6826            if let Some(ref output) = delete.output {
6827                self.generate_output_clause(output)?;
6828            }
6829            self.write_space();
6830            self.write_keyword("FROM");
6831            self.write_space();
6832            self.generate_table(&delete.table)?;
6833        } else if !delete.tables.is_empty() && delete.tables_from_using {
6834            // DELETE FROM t1, t2 USING ... syntax (tables after FROM)
6835            self.write_keyword("DELETE FROM");
6836            self.write_space();
6837            for (i, tbl) in delete.tables.iter().enumerate() {
6838                if i > 0 {
6839                    self.write(", ");
6840                }
6841                self.generate_table(tbl)?;
6842            }
6843        } else if delete.no_from && matches!(self.config.dialect, Some(DialectType::BigQuery)) {
6844            // BigQuery-style DELETE without FROM keyword
6845            self.write_keyword("DELETE");
6846            self.write_space();
6847            self.generate_table(&delete.table)?;
6848        } else {
6849            self.write_keyword("DELETE FROM");
6850            self.write_space();
6851            self.generate_table(&delete.table)?;
6852        }
6853
6854        // ClickHouse: ON CLUSTER clause
6855        if let Some(ref on_cluster) = delete.on_cluster {
6856            self.write_space();
6857            self.generate_on_cluster(on_cluster)?;
6858        }
6859
6860        // FORCE INDEX hint (MySQL)
6861        if let Some(ref idx) = delete.force_index {
6862            self.write_space();
6863            self.write_keyword("FORCE INDEX");
6864            self.write(" (");
6865            self.write(idx);
6866            self.write(")");
6867        }
6868
6869        // Optional alias
6870        if let Some(ref alias) = delete.alias {
6871            self.write_space();
6872            if delete.alias_explicit_as
6873                || matches!(self.config.dialect, Some(DialectType::BigQuery))
6874            {
6875                self.write_keyword("AS");
6876                self.write_space();
6877            }
6878            self.generate_identifier(alias)?;
6879        }
6880
6881        // JOINs (MySQL multi-table) - when NOT tables_from_using, JOINs come before USING
6882        if !delete.tables_from_using {
6883            for join in &delete.joins {
6884                self.generate_join(join)?;
6885            }
6886        }
6887
6888        // USING clause (PostgreSQL/DuckDB/MySQL)
6889        if !delete.using.is_empty() {
6890            self.write_space();
6891            self.write_keyword("USING");
6892            for (i, table) in delete.using.iter().enumerate() {
6893                if i > 0 {
6894                    self.write(",");
6895                }
6896                self.write_space();
6897                // Check if the table has subquery hints (DuckDB USING with subquery)
6898                if !table.hints.is_empty() && table.name.is_empty() {
6899                    // Subquery in USING: (VALUES ...) AS alias(cols)
6900                    self.generate_expression(&table.hints[0])?;
6901                    if let Some(ref alias) = table.alias {
6902                        self.write_space();
6903                        if table.alias_explicit_as {
6904                            self.write_keyword("AS");
6905                            self.write_space();
6906                        }
6907                        self.generate_identifier(alias)?;
6908                        if !table.column_aliases.is_empty() {
6909                            self.write("(");
6910                            for (j, col_alias) in table.column_aliases.iter().enumerate() {
6911                                if j > 0 {
6912                                    self.write(", ");
6913                                }
6914                                self.generate_identifier(col_alias)?;
6915                            }
6916                            self.write(")");
6917                        }
6918                    }
6919                } else {
6920                    self.generate_table(table)?;
6921                }
6922            }
6923        }
6924
6925        // JOINs (MySQL multi-table) - when tables_from_using, JOINs come after USING
6926        if delete.tables_from_using {
6927            for join in &delete.joins {
6928                self.generate_join(join)?;
6929            }
6930        }
6931
6932        // OUTPUT clause (TSQL) - only if not already emitted in the early position
6933        let output_already_emitted =
6934            !delete.tables.is_empty() && !delete.tables_from_using && delete.output.is_some();
6935        if !output_already_emitted {
6936            if let Some(ref output) = delete.output {
6937                self.generate_output_clause(output)?;
6938            }
6939        }
6940
6941        if let Some(where_clause) = &delete.where_clause {
6942            self.write_space();
6943            self.write_keyword("WHERE");
6944            self.write_space();
6945            self.generate_expression(&where_clause.this)?;
6946        }
6947
6948        // ORDER BY clause (MySQL)
6949        if let Some(ref order_by) = delete.order_by {
6950            self.write_space();
6951            self.generate_order_by(order_by)?;
6952        }
6953
6954        // LIMIT clause (MySQL)
6955        if let Some(ref limit) = delete.limit {
6956            self.write_space();
6957            self.write_keyword("LIMIT");
6958            self.write_space();
6959            self.generate_expression(limit)?;
6960        }
6961
6962        // RETURNING clause (PostgreSQL)
6963        if !delete.returning.is_empty() {
6964            self.write_space();
6965            self.write_keyword("RETURNING");
6966            self.write_space();
6967            for (i, expr) in delete.returning.iter().enumerate() {
6968                if i > 0 {
6969                    self.write(", ");
6970                }
6971                self.generate_expression(expr)?;
6972            }
6973        }
6974
6975        Ok(())
6976    }
6977
6978    // ==================== DDL Generation ====================
6979
6980    fn generate_create_table(&mut self, ct: &CreateTable) -> Result<()> {
6981        // Athena: Determine if this is Hive-style DDL or Trino-style DML
6982        // CREATE TABLE AS SELECT uses Trino (double quotes)
6983        // CREATE TABLE (without AS SELECT) and CREATE EXTERNAL TABLE use Hive (backticks)
6984        let saved_athena_hive_context = self.athena_hive_context;
6985        let is_clickhouse = matches!(self.config.dialect, Some(DialectType::ClickHouse));
6986        if matches!(
6987            self.config.dialect,
6988            Some(crate::dialects::DialectType::Athena)
6989        ) {
6990            // Use Hive context if:
6991            // 1. It's an EXTERNAL table, OR
6992            // 2. There's no AS SELECT clause
6993            let is_external = ct
6994                .table_modifier
6995                .as_ref()
6996                .map(|m| m.eq_ignore_ascii_case("EXTERNAL"))
6997                .unwrap_or(false);
6998            let has_as_select = ct.as_select.is_some();
6999            self.athena_hive_context = is_external || !has_as_select;
7000        }
7001
7002        // TSQL: Convert CREATE TABLE AS SELECT to SELECT * INTO table FROM (subquery) AS temp
7003        if matches!(
7004            self.config.dialect,
7005            Some(crate::dialects::DialectType::TSQL)
7006        ) {
7007            if let Some(ref query) = ct.as_select {
7008                // Output WITH CTE clause if present
7009                if let Some(with_cte) = &ct.with_cte {
7010                    self.generate_with(with_cte)?;
7011                    self.write_space();
7012                }
7013
7014                // Generate: SELECT * INTO [table] FROM (subquery) AS temp
7015                self.write_keyword("SELECT");
7016                self.write(" * ");
7017                self.write_keyword("INTO");
7018                self.write_space();
7019
7020                // If temporary, prefix with # for TSQL temp table
7021                if ct.temporary {
7022                    self.write("#");
7023                }
7024                self.generate_table(&ct.name)?;
7025
7026                self.write_space();
7027                self.write_keyword("FROM");
7028                self.write(" (");
7029                // For TSQL, add aliases to select columns to preserve column names
7030                let aliased_query = Self::add_column_aliases_to_query(query.clone());
7031                self.generate_expression(&aliased_query)?;
7032                self.write(") ");
7033                self.write_keyword("AS");
7034                self.write(" temp");
7035                return Ok(());
7036            }
7037        }
7038
7039        // Output WITH CTE clause if present
7040        if let Some(with_cte) = &ct.with_cte {
7041            self.generate_with(with_cte)?;
7042            self.write_space();
7043        }
7044
7045        // Output leading comments before CREATE
7046        for comment in &ct.leading_comments {
7047            self.write_formatted_comment(comment);
7048            self.write(" ");
7049        }
7050        self.write_keyword("CREATE");
7051
7052        if ct.or_replace {
7053            self.write_space();
7054            self.write_keyword("OR REPLACE");
7055        }
7056
7057        if ct.temporary {
7058            self.write_space();
7059            // Oracle uses GLOBAL TEMPORARY TABLE syntax
7060            if matches!(self.config.dialect, Some(DialectType::Oracle)) {
7061                self.write_keyword("GLOBAL TEMPORARY");
7062            } else {
7063                self.write_keyword("TEMPORARY");
7064            }
7065        }
7066
7067        // Table modifier: DYNAMIC, ICEBERG, EXTERNAL, HYBRID, TRANSIENT
7068        let is_dictionary = ct
7069            .table_modifier
7070            .as_ref()
7071            .map(|m| m.eq_ignore_ascii_case("DICTIONARY"))
7072            .unwrap_or(false);
7073        if let Some(ref modifier) = ct.table_modifier {
7074            // TRANSIENT is Snowflake-specific - skip for other dialects
7075            let skip_transient = modifier.eq_ignore_ascii_case("TRANSIENT")
7076                && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None);
7077            // Teradata-specific modifiers: VOLATILE, SET, MULTISET, SET TABLE combinations
7078            let is_teradata_modifier = modifier.eq_ignore_ascii_case("VOLATILE")
7079                || modifier.eq_ignore_ascii_case("SET")
7080                || modifier.eq_ignore_ascii_case("MULTISET")
7081                || modifier.to_uppercase().contains("VOLATILE")
7082                || modifier.to_uppercase().starts_with("SET ")
7083                || modifier.to_uppercase().starts_with("MULTISET ");
7084            let skip_teradata =
7085                is_teradata_modifier && !matches!(self.config.dialect, Some(DialectType::Teradata));
7086            if !skip_transient && !skip_teradata {
7087                self.write_space();
7088                self.write_keyword(modifier);
7089            }
7090        }
7091
7092        if !is_dictionary {
7093            self.write_space();
7094            self.write_keyword("TABLE");
7095        }
7096
7097        if ct.if_not_exists {
7098            self.write_space();
7099            self.write_keyword("IF NOT EXISTS");
7100        }
7101
7102        self.write_space();
7103        self.generate_table(&ct.name)?;
7104
7105        // ClickHouse: ON CLUSTER clause
7106        if let Some(ref on_cluster) = ct.on_cluster {
7107            self.write_space();
7108            self.generate_on_cluster(on_cluster)?;
7109        }
7110
7111        // Teradata: options after table name before column list (comma-separated)
7112        if matches!(
7113            self.config.dialect,
7114            Some(crate::dialects::DialectType::Teradata)
7115        ) && !ct.teradata_post_name_options.is_empty()
7116        {
7117            for opt in &ct.teradata_post_name_options {
7118                self.write(", ");
7119                self.write(opt);
7120            }
7121        }
7122
7123        // Snowflake: COPY GRANTS clause
7124        if ct.copy_grants {
7125            self.write_space();
7126            self.write_keyword("COPY GRANTS");
7127        }
7128
7129        // Snowflake: USING TEMPLATE clause (before columns or AS SELECT)
7130        if let Some(ref using_template) = ct.using_template {
7131            self.write_space();
7132            self.write_keyword("USING TEMPLATE");
7133            self.write_space();
7134            self.generate_expression(using_template)?;
7135            return Ok(());
7136        }
7137
7138        // Handle [SHALLOW | DEEP] CLONE/COPY source_table [AT(...) | BEFORE(...)]
7139        if let Some(ref clone_source) = ct.clone_source {
7140            self.write_space();
7141            if ct.is_copy && self.config.supports_table_copy {
7142                // BigQuery uses COPY
7143                self.write_keyword("COPY");
7144            } else if ct.shallow_clone {
7145                self.write_keyword("SHALLOW CLONE");
7146            } else {
7147                self.write_keyword("CLONE");
7148            }
7149            self.write_space();
7150            self.generate_table(clone_source)?;
7151            // Generate AT/BEFORE time travel clause (stored as Raw expression)
7152            if let Some(ref at_clause) = ct.clone_at_clause {
7153                self.write_space();
7154                self.generate_expression(at_clause)?;
7155            }
7156            return Ok(());
7157        }
7158
7159        // Handle PARTITION OF property
7160        // Output order: PARTITION OF <table> (<columns/constraints>) FOR VALUES ...
7161        // Columns/constraints must appear BETWEEN the table name and the partition bound spec
7162        if let Some(ref partition_of) = ct.partition_of {
7163            self.write_space();
7164
7165            // Extract the PartitionedOfProperty parts to generate them separately
7166            if let Expression::PartitionedOfProperty(ref pop) = partition_of {
7167                // Output: PARTITION OF <table>
7168                self.write_keyword("PARTITION OF");
7169                self.write_space();
7170                self.generate_expression(&pop.this)?;
7171
7172                // Output columns/constraints if present (e.g., (unitsales DEFAULT 0) or (CONSTRAINT ...))
7173                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
7174                    self.write(" (");
7175                    let mut first = true;
7176                    for col in &ct.columns {
7177                        if !first {
7178                            self.write(", ");
7179                        }
7180                        first = false;
7181                        self.generate_column_def(col)?;
7182                    }
7183                    for constraint in &ct.constraints {
7184                        if !first {
7185                            self.write(", ");
7186                        }
7187                        first = false;
7188                        self.generate_table_constraint(constraint)?;
7189                    }
7190                    self.write(")");
7191                }
7192
7193                // Output partition bound spec: FOR VALUES ... or DEFAULT
7194                if let Expression::PartitionBoundSpec(_) = pop.expression.as_ref() {
7195                    self.write_space();
7196                    self.write_keyword("FOR VALUES");
7197                    self.write_space();
7198                    self.generate_expression(&pop.expression)?;
7199                } else {
7200                    self.write_space();
7201                    self.write_keyword("DEFAULT");
7202                }
7203            } else {
7204                // Fallback: generate the whole expression if it's not a PartitionedOfProperty
7205                self.generate_expression(partition_of)?;
7206
7207                // Output columns/constraints if present
7208                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
7209                    self.write(" (");
7210                    let mut first = true;
7211                    for col in &ct.columns {
7212                        if !first {
7213                            self.write(", ");
7214                        }
7215                        first = false;
7216                        self.generate_column_def(col)?;
7217                    }
7218                    for constraint in &ct.constraints {
7219                        if !first {
7220                            self.write(", ");
7221                        }
7222                        first = false;
7223                        self.generate_table_constraint(constraint)?;
7224                    }
7225                    self.write(")");
7226                }
7227            }
7228
7229            // Output table properties (e.g., PARTITION BY RANGE(population))
7230            for prop in &ct.properties {
7231                self.write_space();
7232                self.generate_expression(prop)?;
7233            }
7234
7235            return Ok(());
7236        }
7237
7238        // SQLite: Inline single-column PRIMARY KEY constraints into column definition
7239        // This matches Python sqlglot's behavior for SQLite dialect
7240        self.sqlite_inline_pk_columns.clear();
7241        if matches!(
7242            self.config.dialect,
7243            Some(crate::dialects::DialectType::SQLite)
7244        ) {
7245            for constraint in &ct.constraints {
7246                if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7247                    // Only inline if: single column, no constraint name, and column exists in table
7248                    if columns.len() == 1 && name.is_none() {
7249                        let pk_col_name = columns[0].name.to_lowercase();
7250                        // Check if this column exists in the table
7251                        if ct
7252                            .columns
7253                            .iter()
7254                            .any(|c| c.name.name.to_lowercase() == pk_col_name)
7255                        {
7256                            self.sqlite_inline_pk_columns.insert(pk_col_name);
7257                        }
7258                    }
7259                }
7260            }
7261        }
7262
7263        // Output columns if present (even for CTAS with columns)
7264        if !ct.columns.is_empty() {
7265            if self.config.pretty {
7266                // Pretty print: each column on new line
7267                self.write(" (");
7268                self.write_newline();
7269                self.indent_level += 1;
7270                for (i, col) in ct.columns.iter().enumerate() {
7271                    if i > 0 {
7272                        self.write(",");
7273                        self.write_newline();
7274                    }
7275                    self.write_indent();
7276                    self.generate_column_def(col)?;
7277                }
7278                // Table constraints (skip inlined PRIMARY KEY for SQLite)
7279                for constraint in &ct.constraints {
7280                    // Skip single-column PRIMARY KEY that was inlined for SQLite
7281                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7282                        if columns.len() == 1
7283                            && name.is_none()
7284                            && self
7285                                .sqlite_inline_pk_columns
7286                                .contains(&columns[0].name.to_lowercase())
7287                        {
7288                            continue;
7289                        }
7290                    }
7291                    self.write(",");
7292                    self.write_newline();
7293                    self.write_indent();
7294                    self.generate_table_constraint(constraint)?;
7295                }
7296                self.indent_level -= 1;
7297                self.write_newline();
7298                self.write(")");
7299            } else {
7300                self.write(" (");
7301                for (i, col) in ct.columns.iter().enumerate() {
7302                    if i > 0 {
7303                        self.write(", ");
7304                    }
7305                    self.generate_column_def(col)?;
7306                }
7307                // Table constraints (skip inlined PRIMARY KEY for SQLite)
7308                let mut first_constraint = true;
7309                for constraint in &ct.constraints {
7310                    // Skip single-column PRIMARY KEY that was inlined for SQLite
7311                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7312                        if columns.len() == 1
7313                            && name.is_none()
7314                            && self
7315                                .sqlite_inline_pk_columns
7316                                .contains(&columns[0].name.to_lowercase())
7317                        {
7318                            continue;
7319                        }
7320                    }
7321                    if first_constraint {
7322                        self.write(", ");
7323                        first_constraint = false;
7324                    } else {
7325                        self.write(", ");
7326                    }
7327                    self.generate_table_constraint(constraint)?;
7328                }
7329                self.write(")");
7330            }
7331        } else if !ct.constraints.is_empty() {
7332            // No columns but constraints exist (e.g., CREATE TABLE A LIKE B or CREATE TABLE A TAG (...))
7333            let has_like_only = ct
7334                .constraints
7335                .iter()
7336                .all(|c| matches!(c, TableConstraint::Like { .. }));
7337            let has_tags_only = ct
7338                .constraints
7339                .iter()
7340                .all(|c| matches!(c, TableConstraint::Tags(_)));
7341            // PostgreSQL: CREATE TABLE A (LIKE B INCLUDING ALL) (with parens)
7342            // Most dialects: CREATE TABLE A LIKE B (no parens)
7343            // Snowflake: CREATE TABLE A TAG (...) (no outer parens, but TAG has its own)
7344            let is_pg_like = matches!(
7345                self.config.dialect,
7346                Some(crate::dialects::DialectType::PostgreSQL)
7347                    | Some(crate::dialects::DialectType::CockroachDB)
7348                    | Some(crate::dialects::DialectType::Materialize)
7349                    | Some(crate::dialects::DialectType::RisingWave)
7350                    | Some(crate::dialects::DialectType::Redshift)
7351                    | Some(crate::dialects::DialectType::Presto)
7352                    | Some(crate::dialects::DialectType::Trino)
7353                    | Some(crate::dialects::DialectType::Athena)
7354            );
7355            let use_parens = if has_like_only {
7356                is_pg_like
7357            } else {
7358                !has_tags_only
7359            };
7360            if self.config.pretty && use_parens {
7361                self.write(" (");
7362                self.write_newline();
7363                self.indent_level += 1;
7364                for (i, constraint) in ct.constraints.iter().enumerate() {
7365                    if i > 0 {
7366                        self.write(",");
7367                        self.write_newline();
7368                    }
7369                    self.write_indent();
7370                    self.generate_table_constraint(constraint)?;
7371                }
7372                self.indent_level -= 1;
7373                self.write_newline();
7374                self.write(")");
7375            } else {
7376                if use_parens {
7377                    self.write(" (");
7378                } else {
7379                    self.write_space();
7380                }
7381                for (i, constraint) in ct.constraints.iter().enumerate() {
7382                    if i > 0 {
7383                        self.write(", ");
7384                    }
7385                    self.generate_table_constraint(constraint)?;
7386                }
7387                if use_parens {
7388                    self.write(")");
7389                }
7390            }
7391        }
7392
7393        // TSQL ON filegroup or ON filegroup (partition_column) clause
7394        if let Some(ref on_prop) = ct.on_property {
7395            self.write(" ");
7396            self.write_keyword("ON");
7397            self.write(" ");
7398            self.generate_expression(&on_prop.this)?;
7399        }
7400
7401        // Output SchemaCommentProperty BEFORE WITH properties (Presto/Hive/Spark style)
7402        // For ClickHouse, SchemaCommentProperty goes after AS SELECT, handled later
7403        if !is_clickhouse {
7404            for prop in &ct.properties {
7405                if let Expression::SchemaCommentProperty(_) = prop {
7406                    if self.config.pretty {
7407                        self.write_newline();
7408                    } else {
7409                        self.write_space();
7410                    }
7411                    self.generate_expression(prop)?;
7412                }
7413            }
7414        }
7415
7416        // WITH properties (output after columns if columns exist, otherwise before AS)
7417        if !ct.with_properties.is_empty() {
7418            // Snowflake ICEBERG/DYNAMIC TABLE: output properties inline (space-separated, no WITH wrapper)
7419            let is_snowflake_special_table = matches!(
7420                self.config.dialect,
7421                Some(crate::dialects::DialectType::Snowflake)
7422            ) && (ct.table_modifier.as_deref() == Some("ICEBERG")
7423                || ct.table_modifier.as_deref() == Some("DYNAMIC"));
7424            if is_snowflake_special_table {
7425                for (key, value) in &ct.with_properties {
7426                    self.write_space();
7427                    self.write(key);
7428                    self.write("=");
7429                    self.write(value);
7430                }
7431            } else if self.config.pretty {
7432                self.write_newline();
7433                self.write_keyword("WITH");
7434                self.write(" (");
7435                self.write_newline();
7436                self.indent_level += 1;
7437                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
7438                    if i > 0 {
7439                        self.write(",");
7440                        self.write_newline();
7441                    }
7442                    self.write_indent();
7443                    self.write(key);
7444                    self.write("=");
7445                    self.write(value);
7446                }
7447                self.indent_level -= 1;
7448                self.write_newline();
7449                self.write(")");
7450            } else {
7451                self.write_space();
7452                self.write_keyword("WITH");
7453                self.write(" (");
7454                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
7455                    if i > 0 {
7456                        self.write(", ");
7457                    }
7458                    self.write(key);
7459                    self.write("=");
7460                    self.write(value);
7461                }
7462                self.write(")");
7463            }
7464        }
7465
7466        let (pre_as_properties, post_as_properties): (Vec<&Expression>, Vec<&Expression>) =
7467            if is_clickhouse && ct.as_select.is_some() {
7468                let mut pre = Vec::new();
7469                let mut post = Vec::new();
7470                for prop in &ct.properties {
7471                    if matches!(prop, Expression::SchemaCommentProperty(_)) {
7472                        post.push(prop);
7473                    } else {
7474                        pre.push(prop);
7475                    }
7476                }
7477                (pre, post)
7478            } else {
7479                (ct.properties.iter().collect(), Vec::new())
7480            };
7481
7482        // Table properties like DEFAULT COLLATE (BigQuery), OPTIONS (...), TBLPROPERTIES (...), or PROPERTIES (...)
7483        for prop in pre_as_properties {
7484            // SchemaCommentProperty was already output before WITH properties (except for ClickHouse)
7485            if !is_clickhouse && matches!(prop, Expression::SchemaCommentProperty(_)) {
7486                continue;
7487            }
7488            if self.config.pretty {
7489                self.write_newline();
7490            } else {
7491                self.write_space();
7492            }
7493            // BigQuery: Properties containing OPTIONS should be wrapped with OPTIONS (...)
7494            // Hive: Properties should be wrapped with TBLPROPERTIES (...)
7495            // Doris/StarRocks: Properties should be wrapped with PROPERTIES (...)
7496            if let Expression::Properties(props) = prop {
7497                let is_hive_dialect = matches!(
7498                    self.config.dialect,
7499                    Some(crate::dialects::DialectType::Hive)
7500                        | Some(crate::dialects::DialectType::Spark)
7501                        | Some(crate::dialects::DialectType::Databricks)
7502                        | Some(crate::dialects::DialectType::Athena)
7503                );
7504                let is_doris_starrocks = matches!(
7505                    self.config.dialect,
7506                    Some(crate::dialects::DialectType::Doris)
7507                        | Some(crate::dialects::DialectType::StarRocks)
7508                );
7509                if is_hive_dialect {
7510                    self.generate_tblproperties_clause(&props.expressions)?;
7511                } else if is_doris_starrocks {
7512                    self.generate_properties_clause(&props.expressions)?;
7513                } else {
7514                    self.generate_options_clause(&props.expressions)?;
7515                }
7516            } else {
7517                self.generate_expression(prop)?;
7518            }
7519        }
7520
7521        // Post-table properties like TSQL WITH(SYSTEM_VERSIONING=ON(...)) or Doris PROPERTIES
7522        for prop in &ct.post_table_properties {
7523            if let Expression::WithSystemVersioningProperty(ref svp) = prop {
7524                self.write(" WITH(");
7525                self.generate_system_versioning_content(svp)?;
7526                self.write(")");
7527            } else if let Expression::Properties(props) = prop {
7528                // Doris/StarRocks: PROPERTIES ('key'='value', ...) in post_table_properties
7529                let is_doris_starrocks = matches!(
7530                    self.config.dialect,
7531                    Some(crate::dialects::DialectType::Doris)
7532                        | Some(crate::dialects::DialectType::StarRocks)
7533                );
7534                self.write_space();
7535                if is_doris_starrocks {
7536                    self.generate_properties_clause(&props.expressions)?;
7537                } else {
7538                    self.generate_options_clause(&props.expressions)?;
7539                }
7540            } else {
7541                self.write_space();
7542                self.generate_expression(prop)?;
7543            }
7544        }
7545
7546        // StarRocks ROLLUP property: ROLLUP (r1(col1, col2), r2(col1))
7547        // Only output for StarRocks target
7548        if let Some(ref rollup) = ct.rollup {
7549            if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
7550                self.write_space();
7551                self.generate_rollup_property(rollup)?;
7552            }
7553        }
7554
7555        // MySQL table options (ENGINE=val, AUTO_INCREMENT=val, etc.)
7556        // Only output for MySQL-compatible dialects; strip for others during transpilation
7557        // COMMENT is also used by Hive/Spark so we selectively preserve it
7558        let is_mysql_compatible = matches!(
7559            self.config.dialect,
7560            Some(DialectType::MySQL)
7561                | Some(DialectType::SingleStore)
7562                | Some(DialectType::Doris)
7563                | Some(DialectType::StarRocks)
7564                | None
7565        );
7566        let is_hive_compatible = matches!(
7567            self.config.dialect,
7568            Some(DialectType::Hive)
7569                | Some(DialectType::Spark)
7570                | Some(DialectType::Databricks)
7571                | Some(DialectType::Athena)
7572        );
7573        let mysql_pretty_options =
7574            self.config.pretty && matches!(self.config.dialect, Some(DialectType::MySQL));
7575        for (key, value) in &ct.mysql_table_options {
7576            // Skip non-MySQL-specific options for non-MySQL targets
7577            let should_output = if is_mysql_compatible {
7578                true
7579            } else if is_hive_compatible && key == "COMMENT" {
7580                true // COMMENT is valid in Hive/Spark table definitions
7581            } else {
7582                false
7583            };
7584            if should_output {
7585                if mysql_pretty_options {
7586                    self.write_newline();
7587                    self.write_indent();
7588                } else {
7589                    self.write_space();
7590                }
7591                self.write_keyword(key);
7592                // StarRocks/Doris: COMMENT 'value' (no =), others: COMMENT='value'
7593                if key == "COMMENT" && !self.config.schema_comment_with_eq {
7594                    self.write_space();
7595                } else {
7596                    self.write("=");
7597                }
7598                self.write(value);
7599            }
7600        }
7601
7602        // Spark/Databricks: USING PARQUET for temporary tables that don't already have a storage format
7603        if ct.temporary
7604            && matches!(
7605                self.config.dialect,
7606                Some(DialectType::Spark) | Some(DialectType::Databricks)
7607            )
7608            && ct.as_select.is_none()
7609        {
7610            self.write_space();
7611            self.write_keyword("USING PARQUET");
7612        }
7613
7614        // PostgreSQL INHERITS clause
7615        if !ct.inherits.is_empty() {
7616            self.write_space();
7617            self.write_keyword("INHERITS");
7618            self.write(" (");
7619            for (i, parent) in ct.inherits.iter().enumerate() {
7620                if i > 0 {
7621                    self.write(", ");
7622                }
7623                self.generate_table(parent)?;
7624            }
7625            self.write(")");
7626        }
7627
7628        // CREATE TABLE AS SELECT
7629        if let Some(ref query) = ct.as_select {
7630            self.write_space();
7631            self.write_keyword("AS");
7632            self.write_space();
7633            if ct.as_select_parenthesized {
7634                self.write("(");
7635            }
7636            self.generate_expression(query)?;
7637            if ct.as_select_parenthesized {
7638                self.write(")");
7639            }
7640
7641            // Teradata: WITH DATA / WITH NO DATA
7642            if let Some(with_data) = ct.with_data {
7643                self.write_space();
7644                self.write_keyword("WITH");
7645                if !with_data {
7646                    self.write_space();
7647                    self.write_keyword("NO");
7648                }
7649                self.write_space();
7650                self.write_keyword("DATA");
7651            }
7652
7653            // Teradata: AND STATISTICS / AND NO STATISTICS
7654            if let Some(with_statistics) = ct.with_statistics {
7655                self.write_space();
7656                self.write_keyword("AND");
7657                if !with_statistics {
7658                    self.write_space();
7659                    self.write_keyword("NO");
7660                }
7661                self.write_space();
7662                self.write_keyword("STATISTICS");
7663            }
7664
7665            // Teradata: Index specifications
7666            for index in &ct.teradata_indexes {
7667                self.write_space();
7668                match index.kind {
7669                    TeradataIndexKind::NoPrimary => {
7670                        self.write_keyword("NO PRIMARY INDEX");
7671                    }
7672                    TeradataIndexKind::Primary => {
7673                        self.write_keyword("PRIMARY INDEX");
7674                    }
7675                    TeradataIndexKind::PrimaryAmp => {
7676                        self.write_keyword("PRIMARY AMP INDEX");
7677                    }
7678                    TeradataIndexKind::Unique => {
7679                        self.write_keyword("UNIQUE INDEX");
7680                    }
7681                    TeradataIndexKind::UniquePrimary => {
7682                        self.write_keyword("UNIQUE PRIMARY INDEX");
7683                    }
7684                    TeradataIndexKind::Secondary => {
7685                        self.write_keyword("INDEX");
7686                    }
7687                }
7688                // Output index name if present
7689                if let Some(ref name) = index.name {
7690                    self.write_space();
7691                    self.write(name);
7692                }
7693                // Output columns if present
7694                if !index.columns.is_empty() {
7695                    self.write(" (");
7696                    for (i, col) in index.columns.iter().enumerate() {
7697                        if i > 0 {
7698                            self.write(", ");
7699                        }
7700                        self.write(col);
7701                    }
7702                    self.write(")");
7703                }
7704            }
7705
7706            // Teradata: ON COMMIT behavior for volatile tables
7707            if let Some(ref on_commit) = ct.on_commit {
7708                self.write_space();
7709                self.write_keyword("ON COMMIT");
7710                self.write_space();
7711                match on_commit {
7712                    OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
7713                    OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
7714                }
7715            }
7716
7717            if !post_as_properties.is_empty() {
7718                for prop in post_as_properties {
7719                    self.write_space();
7720                    self.generate_expression(prop)?;
7721                }
7722            }
7723
7724            // Restore Athena Hive context before early return
7725            self.athena_hive_context = saved_athena_hive_context;
7726            return Ok(());
7727        }
7728
7729        // ON COMMIT behavior (for non-CTAS tables)
7730        if let Some(ref on_commit) = ct.on_commit {
7731            self.write_space();
7732            self.write_keyword("ON COMMIT");
7733            self.write_space();
7734            match on_commit {
7735                OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
7736                OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
7737            }
7738        }
7739
7740        // Restore Athena Hive context
7741        self.athena_hive_context = saved_athena_hive_context;
7742
7743        Ok(())
7744    }
7745
7746    /// Generate column definition as an expression (for ROWS FROM alias columns, XMLTABLE/JSON_TABLE)
7747    /// Outputs: "col_name" TYPE [PATH 'xpath'] (not the full CREATE TABLE column definition)
7748    fn generate_column_def_expr(&mut self, col: &ColumnDef) -> Result<()> {
7749        // Output column name
7750        self.generate_identifier(&col.name)?;
7751        // Output data type if known
7752        if !matches!(col.data_type, DataType::Unknown) {
7753            self.write_space();
7754            self.generate_data_type(&col.data_type)?;
7755        }
7756        // Output PATH constraint if present (for XMLTABLE/JSON_TABLE columns)
7757        for constraint in &col.constraints {
7758            if let ColumnConstraint::Path(path_expr) = constraint {
7759                self.write_space();
7760                self.write_keyword("PATH");
7761                self.write_space();
7762                self.generate_expression(path_expr)?;
7763            }
7764        }
7765        Ok(())
7766    }
7767
7768    fn generate_column_def(&mut self, col: &ColumnDef) -> Result<()> {
7769        // Check if this is a TSQL computed column (no data type)
7770        let has_computed_no_type = matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
7771            && col
7772                .constraints
7773                .iter()
7774                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
7775        // Some dialects (notably TSQL/Fabric) do not include an explicit type for computed columns.
7776        let omit_computed_type = !self.config.computed_column_with_type
7777            && col
7778                .constraints
7779                .iter()
7780                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
7781
7782        // Check if this is a partition column spec (no data type, type is Unknown)
7783        // This is used in PostgreSQL PARTITION OF syntax where columns only have constraints
7784        let is_partition_column_spec = matches!(col.data_type, DataType::Unknown);
7785
7786        // Check if this is a DYNAMIC TABLE column (no data type, empty Custom name, no constraints)
7787        // Also check the no_type flag for SQLite columns without types
7788        let has_no_type = col.no_type
7789            || (matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
7790                && col.constraints.is_empty());
7791
7792        self.generate_identifier(&col.name)?;
7793
7794        // Check for SERIAL/BIGSERIAL/SMALLSERIAL expansion for Materialize and PostgreSQL
7795        let serial_expansion = if matches!(
7796            self.config.dialect,
7797            Some(DialectType::Materialize) | Some(DialectType::PostgreSQL)
7798        ) {
7799            if let DataType::Custom { ref name } = col.data_type {
7800                match name.to_uppercase().as_str() {
7801                    "SERIAL" => Some("INT"),
7802                    "BIGSERIAL" => Some("BIGINT"),
7803                    "SMALLSERIAL" => Some("SMALLINT"),
7804                    _ => None,
7805                }
7806            } else {
7807                None
7808            }
7809        } else {
7810            None
7811        };
7812
7813        if !has_computed_no_type && !omit_computed_type && !is_partition_column_spec && !has_no_type
7814        {
7815            self.write_space();
7816            // ClickHouse CREATE TABLE column types: suppress automatic Nullable wrapping
7817            // since ClickHouse uses explicit Nullable() in its type system.
7818            let saved_nullable_depth = self.clickhouse_nullable_depth;
7819            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
7820                self.clickhouse_nullable_depth = -1;
7821            }
7822            if let Some(int_type) = serial_expansion {
7823                // SERIAL -> INT (+ constraints added below)
7824                self.write_keyword(int_type);
7825            } else if col.unsigned && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
7826                // For DuckDB: convert unsigned integer types to their unsigned equivalents
7827                let unsigned_type = match &col.data_type {
7828                    DataType::Int { .. } => Some("UINTEGER"),
7829                    DataType::BigInt { .. } => Some("UBIGINT"),
7830                    DataType::SmallInt { .. } => Some("USMALLINT"),
7831                    DataType::TinyInt { .. } => Some("UTINYINT"),
7832                    _ => None,
7833                };
7834                if let Some(utype) = unsigned_type {
7835                    self.write_keyword(utype);
7836                } else {
7837                    self.generate_data_type(&col.data_type)?;
7838                }
7839            } else {
7840                self.generate_data_type(&col.data_type)?;
7841            }
7842            self.clickhouse_nullable_depth = saved_nullable_depth;
7843        }
7844
7845        // MySQL type modifiers (must come right after data type)
7846        // Skip UNSIGNED for DuckDB (already mapped to unsigned type above)
7847        if col.unsigned && !matches!(self.config.dialect, Some(DialectType::DuckDB)) {
7848            self.write_space();
7849            self.write_keyword("UNSIGNED");
7850        }
7851        if col.zerofill {
7852            self.write_space();
7853            self.write_keyword("ZEROFILL");
7854        }
7855
7856        // Teradata column attributes (must come right after data type, in specific order)
7857        // ORDER: CHARACTER SET, UPPERCASE, CASESPECIFIC, FORMAT, TITLE, INLINE LENGTH, COMPRESS
7858
7859        if let Some(ref charset) = col.character_set {
7860            self.write_space();
7861            self.write_keyword("CHARACTER SET");
7862            self.write_space();
7863            self.write(charset);
7864        }
7865
7866        if col.uppercase {
7867            self.write_space();
7868            self.write_keyword("UPPERCASE");
7869        }
7870
7871        if let Some(casespecific) = col.casespecific {
7872            self.write_space();
7873            if casespecific {
7874                self.write_keyword("CASESPECIFIC");
7875            } else {
7876                self.write_keyword("NOT CASESPECIFIC");
7877            }
7878        }
7879
7880        if let Some(ref format) = col.format {
7881            self.write_space();
7882            self.write_keyword("FORMAT");
7883            self.write(" '");
7884            self.write(format);
7885            self.write("'");
7886        }
7887
7888        if let Some(ref title) = col.title {
7889            self.write_space();
7890            self.write_keyword("TITLE");
7891            self.write(" '");
7892            self.write(title);
7893            self.write("'");
7894        }
7895
7896        if let Some(length) = col.inline_length {
7897            self.write_space();
7898            self.write_keyword("INLINE LENGTH");
7899            self.write(" ");
7900            self.write(&length.to_string());
7901        }
7902
7903        if let Some(ref compress) = col.compress {
7904            self.write_space();
7905            self.write_keyword("COMPRESS");
7906            if !compress.is_empty() {
7907                // Single string literal: output without parentheses (Teradata syntax)
7908                if compress.len() == 1 {
7909                    if let Expression::Literal(Literal::String(_)) = &compress[0] {
7910                        self.write_space();
7911                        self.generate_expression(&compress[0])?;
7912                    } else {
7913                        self.write(" (");
7914                        self.generate_expression(&compress[0])?;
7915                        self.write(")");
7916                    }
7917                } else {
7918                    self.write(" (");
7919                    for (i, val) in compress.iter().enumerate() {
7920                        if i > 0 {
7921                            self.write(", ");
7922                        }
7923                        self.generate_expression(val)?;
7924                    }
7925                    self.write(")");
7926                }
7927            }
7928        }
7929
7930        // Column constraints - output in original order if constraint_order is populated
7931        // Otherwise fall back to legacy fixed order for backward compatibility
7932        if !col.constraint_order.is_empty() {
7933            // Use constraint_order for original ordering
7934            // Track indices for constraints stored in the constraints Vec
7935            let mut references_idx = 0;
7936            let mut check_idx = 0;
7937            let mut generated_idx = 0;
7938            let mut collate_idx = 0;
7939            let mut comment_idx = 0;
7940            // The preprocessing in dialects/mod.rs now handles the correct ordering of
7941            // NOT NULL relative to IDENTITY for PostgreSQL, so no deferral needed here.
7942            let defer_not_null_after_identity = false;
7943            let mut pending_not_null_after_identity = false;
7944
7945            for constraint_type in &col.constraint_order {
7946                match constraint_type {
7947                    ConstraintType::PrimaryKey => {
7948                        // Materialize doesn't support PRIMARY KEY column constraints
7949                        if col.primary_key
7950                            && !matches!(self.config.dialect, Some(DialectType::Materialize))
7951                        {
7952                            if let Some(ref cname) = col.primary_key_constraint_name {
7953                                self.write_space();
7954                                self.write_keyword("CONSTRAINT");
7955                                self.write_space();
7956                                self.write(cname);
7957                            }
7958                            self.write_space();
7959                            self.write_keyword("PRIMARY KEY");
7960                            if let Some(ref order) = col.primary_key_order {
7961                                self.write_space();
7962                                match order {
7963                                    SortOrder::Asc => self.write_keyword("ASC"),
7964                                    SortOrder::Desc => self.write_keyword("DESC"),
7965                                }
7966                            }
7967                        }
7968                    }
7969                    ConstraintType::Unique => {
7970                        if col.unique {
7971                            if let Some(ref cname) = col.unique_constraint_name {
7972                                self.write_space();
7973                                self.write_keyword("CONSTRAINT");
7974                                self.write_space();
7975                                self.write(cname);
7976                            }
7977                            self.write_space();
7978                            self.write_keyword("UNIQUE");
7979                            // PostgreSQL 15+: NULLS NOT DISTINCT
7980                            if col.unique_nulls_not_distinct {
7981                                self.write(" NULLS NOT DISTINCT");
7982                            }
7983                        }
7984                    }
7985                    ConstraintType::NotNull => {
7986                        if col.nullable == Some(false) {
7987                            if defer_not_null_after_identity {
7988                                pending_not_null_after_identity = true;
7989                                continue;
7990                            }
7991                            if let Some(ref cname) = col.not_null_constraint_name {
7992                                self.write_space();
7993                                self.write_keyword("CONSTRAINT");
7994                                self.write_space();
7995                                self.write(cname);
7996                            }
7997                            self.write_space();
7998                            self.write_keyword("NOT NULL");
7999                        }
8000                    }
8001                    ConstraintType::Null => {
8002                        if col.nullable == Some(true) {
8003                            self.write_space();
8004                            self.write_keyword("NULL");
8005                        }
8006                    }
8007                    ConstraintType::Default => {
8008                        if let Some(ref default) = col.default {
8009                            self.write_space();
8010                            self.write_keyword("DEFAULT");
8011                            self.write_space();
8012                            self.generate_expression(default)?;
8013                        }
8014                    }
8015                    ConstraintType::AutoIncrement => {
8016                        if col.auto_increment {
8017                            // DuckDB doesn't support AUTO_INCREMENT - skip entirely
8018                            if matches!(
8019                                self.config.dialect,
8020                                Some(crate::dialects::DialectType::DuckDB)
8021                            ) {
8022                                // Skip - DuckDB uses sequences or rowid instead
8023                            } else if matches!(
8024                                self.config.dialect,
8025                                Some(crate::dialects::DialectType::Materialize)
8026                            ) {
8027                                // Materialize strips AUTO_INCREMENT but adds NOT NULL
8028                                if !matches!(col.nullable, Some(false)) {
8029                                    self.write_space();
8030                                    self.write_keyword("NOT NULL");
8031                                }
8032                            } else if matches!(
8033                                self.config.dialect,
8034                                Some(crate::dialects::DialectType::PostgreSQL)
8035                            ) {
8036                                // PostgreSQL: AUTO_INCREMENT -> GENERATED BY DEFAULT AS IDENTITY
8037                                self.write_space();
8038                                self.generate_auto_increment_keyword(col)?;
8039                            } else {
8040                                self.write_space();
8041                                self.generate_auto_increment_keyword(col)?;
8042                                if pending_not_null_after_identity {
8043                                    self.write_space();
8044                                    self.write_keyword("NOT NULL");
8045                                    pending_not_null_after_identity = false;
8046                                }
8047                            }
8048                        } // close else for DuckDB skip
8049                    }
8050                    ConstraintType::References => {
8051                        // Find next References constraint
8052                        while references_idx < col.constraints.len() {
8053                            if let ColumnConstraint::References(fk_ref) =
8054                                &col.constraints[references_idx]
8055                            {
8056                                // CONSTRAINT name if present
8057                                if let Some(ref name) = fk_ref.constraint_name {
8058                                    self.write_space();
8059                                    self.write_keyword("CONSTRAINT");
8060                                    self.write_space();
8061                                    self.write(name);
8062                                }
8063                                self.write_space();
8064                                if fk_ref.has_foreign_key_keywords {
8065                                    self.write_keyword("FOREIGN KEY");
8066                                    self.write_space();
8067                                }
8068                                self.write_keyword("REFERENCES");
8069                                self.write_space();
8070                                self.generate_table(&fk_ref.table)?;
8071                                if !fk_ref.columns.is_empty() {
8072                                    self.write(" (");
8073                                    for (i, c) in fk_ref.columns.iter().enumerate() {
8074                                        if i > 0 {
8075                                            self.write(", ");
8076                                        }
8077                                        self.generate_identifier(c)?;
8078                                    }
8079                                    self.write(")");
8080                                }
8081                                self.generate_referential_actions(fk_ref)?;
8082                                references_idx += 1;
8083                                break;
8084                            }
8085                            references_idx += 1;
8086                        }
8087                    }
8088                    ConstraintType::Check => {
8089                        // Find next Check constraint
8090                        while check_idx < col.constraints.len() {
8091                            if let ColumnConstraint::Check(expr) = &col.constraints[check_idx] {
8092                                // Output CONSTRAINT name if present (only for first CHECK)
8093                                if check_idx == 0 {
8094                                    if let Some(ref cname) = col.check_constraint_name {
8095                                        self.write_space();
8096                                        self.write_keyword("CONSTRAINT");
8097                                        self.write_space();
8098                                        self.write(cname);
8099                                    }
8100                                }
8101                                self.write_space();
8102                                self.write_keyword("CHECK");
8103                                self.write(" (");
8104                                self.generate_expression(expr)?;
8105                                self.write(")");
8106                                check_idx += 1;
8107                                break;
8108                            }
8109                            check_idx += 1;
8110                        }
8111                    }
8112                    ConstraintType::GeneratedAsIdentity => {
8113                        // Find next GeneratedAsIdentity constraint
8114                        while generated_idx < col.constraints.len() {
8115                            if let ColumnConstraint::GeneratedAsIdentity(gen) =
8116                                &col.constraints[generated_idx]
8117                            {
8118                                self.write_space();
8119                                // Redshift uses IDENTITY(start, increment) syntax
8120                                if matches!(
8121                                    self.config.dialect,
8122                                    Some(crate::dialects::DialectType::Redshift)
8123                                ) {
8124                                    self.write_keyword("IDENTITY");
8125                                    self.write("(");
8126                                    if let Some(ref start) = gen.start {
8127                                        self.generate_expression(start)?;
8128                                    } else {
8129                                        self.write("0");
8130                                    }
8131                                    self.write(", ");
8132                                    if let Some(ref incr) = gen.increment {
8133                                        self.generate_expression(incr)?;
8134                                    } else {
8135                                        self.write("1");
8136                                    }
8137                                    self.write(")");
8138                                } else {
8139                                    self.write_keyword("GENERATED");
8140                                    if gen.always {
8141                                        self.write_space();
8142                                        self.write_keyword("ALWAYS");
8143                                    } else {
8144                                        self.write_space();
8145                                        self.write_keyword("BY DEFAULT");
8146                                        if gen.on_null {
8147                                            self.write_space();
8148                                            self.write_keyword("ON NULL");
8149                                        }
8150                                    }
8151                                    self.write_space();
8152                                    self.write_keyword("AS IDENTITY");
8153
8154                                    let has_options = gen.start.is_some()
8155                                        || gen.increment.is_some()
8156                                        || gen.minvalue.is_some()
8157                                        || gen.maxvalue.is_some()
8158                                        || gen.cycle.is_some();
8159                                    if has_options {
8160                                        self.write(" (");
8161                                        let mut first = true;
8162                                        if let Some(ref start) = gen.start {
8163                                            if !first {
8164                                                self.write(" ");
8165                                            }
8166                                            first = false;
8167                                            self.write_keyword("START WITH");
8168                                            self.write_space();
8169                                            self.generate_expression(start)?;
8170                                        }
8171                                        if let Some(ref incr) = gen.increment {
8172                                            if !first {
8173                                                self.write(" ");
8174                                            }
8175                                            first = false;
8176                                            self.write_keyword("INCREMENT BY");
8177                                            self.write_space();
8178                                            self.generate_expression(incr)?;
8179                                        }
8180                                        if let Some(ref minv) = gen.minvalue {
8181                                            if !first {
8182                                                self.write(" ");
8183                                            }
8184                                            first = false;
8185                                            self.write_keyword("MINVALUE");
8186                                            self.write_space();
8187                                            self.generate_expression(minv)?;
8188                                        }
8189                                        if let Some(ref maxv) = gen.maxvalue {
8190                                            if !first {
8191                                                self.write(" ");
8192                                            }
8193                                            first = false;
8194                                            self.write_keyword("MAXVALUE");
8195                                            self.write_space();
8196                                            self.generate_expression(maxv)?;
8197                                        }
8198                                        if let Some(cycle) = gen.cycle {
8199                                            if !first {
8200                                                self.write(" ");
8201                                            }
8202                                            if cycle {
8203                                                self.write_keyword("CYCLE");
8204                                            } else {
8205                                                self.write_keyword("NO CYCLE");
8206                                            }
8207                                        }
8208                                        self.write(")");
8209                                    }
8210                                }
8211                                generated_idx += 1;
8212                                break;
8213                            }
8214                            generated_idx += 1;
8215                        }
8216                    }
8217                    ConstraintType::Collate => {
8218                        // Find next Collate constraint
8219                        while collate_idx < col.constraints.len() {
8220                            if let ColumnConstraint::Collate(collation) =
8221                                &col.constraints[collate_idx]
8222                            {
8223                                self.write_space();
8224                                self.write_keyword("COLLATE");
8225                                self.write_space();
8226                                self.generate_identifier(collation)?;
8227                                collate_idx += 1;
8228                                break;
8229                            }
8230                            collate_idx += 1;
8231                        }
8232                    }
8233                    ConstraintType::Comment => {
8234                        // Find next Comment constraint
8235                        while comment_idx < col.constraints.len() {
8236                            if let ColumnConstraint::Comment(comment) =
8237                                &col.constraints[comment_idx]
8238                            {
8239                                self.write_space();
8240                                self.write_keyword("COMMENT");
8241                                self.write_space();
8242                                self.generate_string_literal(comment)?;
8243                                comment_idx += 1;
8244                                break;
8245                            }
8246                            comment_idx += 1;
8247                        }
8248                    }
8249                    ConstraintType::Tags => {
8250                        // Find next Tags constraint (Snowflake)
8251                        for constraint in &col.constraints {
8252                            if let ColumnConstraint::Tags(tags) = constraint {
8253                                self.write_space();
8254                                self.write_keyword("TAG");
8255                                self.write(" (");
8256                                for (i, expr) in tags.expressions.iter().enumerate() {
8257                                    if i > 0 {
8258                                        self.write(", ");
8259                                    }
8260                                    self.generate_expression(expr)?;
8261                                }
8262                                self.write(")");
8263                                break;
8264                            }
8265                        }
8266                    }
8267                    ConstraintType::ComputedColumn => {
8268                        // Find next ComputedColumn constraint
8269                        for constraint in &col.constraints {
8270                            if let ColumnConstraint::ComputedColumn(cc) = constraint {
8271                                self.write_space();
8272                                self.generate_computed_column_inline(cc)?;
8273                                break;
8274                            }
8275                        }
8276                    }
8277                    ConstraintType::GeneratedAsRow => {
8278                        // Find next GeneratedAsRow constraint
8279                        for constraint in &col.constraints {
8280                            if let ColumnConstraint::GeneratedAsRow(gar) = constraint {
8281                                self.write_space();
8282                                self.generate_generated_as_row_inline(gar)?;
8283                                break;
8284                            }
8285                        }
8286                    }
8287                    ConstraintType::OnUpdate => {
8288                        if let Some(ref expr) = col.on_update {
8289                            self.write_space();
8290                            self.write_keyword("ON UPDATE");
8291                            self.write_space();
8292                            self.generate_expression(expr)?;
8293                        }
8294                    }
8295                    ConstraintType::Encode => {
8296                        if let Some(ref encoding) = col.encoding {
8297                            self.write_space();
8298                            self.write_keyword("ENCODE");
8299                            self.write_space();
8300                            self.write(encoding);
8301                        }
8302                    }
8303                    ConstraintType::Path => {
8304                        // Find next Path constraint
8305                        for constraint in &col.constraints {
8306                            if let ColumnConstraint::Path(path_expr) = constraint {
8307                                self.write_space();
8308                                self.write_keyword("PATH");
8309                                self.write_space();
8310                                self.generate_expression(path_expr)?;
8311                                break;
8312                            }
8313                        }
8314                    }
8315                }
8316            }
8317            if pending_not_null_after_identity {
8318                self.write_space();
8319                self.write_keyword("NOT NULL");
8320            }
8321        } else {
8322            // Legacy fixed order for backward compatibility
8323            if col.primary_key {
8324                self.write_space();
8325                self.write_keyword("PRIMARY KEY");
8326                if let Some(ref order) = col.primary_key_order {
8327                    self.write_space();
8328                    match order {
8329                        SortOrder::Asc => self.write_keyword("ASC"),
8330                        SortOrder::Desc => self.write_keyword("DESC"),
8331                    }
8332                }
8333            }
8334
8335            if col.unique {
8336                self.write_space();
8337                self.write_keyword("UNIQUE");
8338                // PostgreSQL 15+: NULLS NOT DISTINCT
8339                if col.unique_nulls_not_distinct {
8340                    self.write(" NULLS NOT DISTINCT");
8341                }
8342            }
8343
8344            match col.nullable {
8345                Some(false) => {
8346                    self.write_space();
8347                    self.write_keyword("NOT NULL");
8348                }
8349                Some(true) => {
8350                    self.write_space();
8351                    self.write_keyword("NULL");
8352                }
8353                None => {}
8354            }
8355
8356            if let Some(ref default) = col.default {
8357                self.write_space();
8358                self.write_keyword("DEFAULT");
8359                self.write_space();
8360                self.generate_expression(default)?;
8361            }
8362
8363            if col.auto_increment {
8364                self.write_space();
8365                self.generate_auto_increment_keyword(col)?;
8366            }
8367
8368            // Column-level constraints from Vec
8369            for constraint in &col.constraints {
8370                match constraint {
8371                    ColumnConstraint::References(fk_ref) => {
8372                        self.write_space();
8373                        if fk_ref.has_foreign_key_keywords {
8374                            self.write_keyword("FOREIGN KEY");
8375                            self.write_space();
8376                        }
8377                        self.write_keyword("REFERENCES");
8378                        self.write_space();
8379                        self.generate_table(&fk_ref.table)?;
8380                        if !fk_ref.columns.is_empty() {
8381                            self.write(" (");
8382                            for (i, c) in fk_ref.columns.iter().enumerate() {
8383                                if i > 0 {
8384                                    self.write(", ");
8385                                }
8386                                self.generate_identifier(c)?;
8387                            }
8388                            self.write(")");
8389                        }
8390                        self.generate_referential_actions(fk_ref)?;
8391                    }
8392                    ColumnConstraint::Check(expr) => {
8393                        self.write_space();
8394                        self.write_keyword("CHECK");
8395                        self.write(" (");
8396                        self.generate_expression(expr)?;
8397                        self.write(")");
8398                    }
8399                    ColumnConstraint::GeneratedAsIdentity(gen) => {
8400                        self.write_space();
8401                        // Redshift uses IDENTITY(start, increment) syntax
8402                        if matches!(
8403                            self.config.dialect,
8404                            Some(crate::dialects::DialectType::Redshift)
8405                        ) {
8406                            self.write_keyword("IDENTITY");
8407                            self.write("(");
8408                            if let Some(ref start) = gen.start {
8409                                self.generate_expression(start)?;
8410                            } else {
8411                                self.write("0");
8412                            }
8413                            self.write(", ");
8414                            if let Some(ref incr) = gen.increment {
8415                                self.generate_expression(incr)?;
8416                            } else {
8417                                self.write("1");
8418                            }
8419                            self.write(")");
8420                        } else {
8421                            self.write_keyword("GENERATED");
8422                            if gen.always {
8423                                self.write_space();
8424                                self.write_keyword("ALWAYS");
8425                            } else {
8426                                self.write_space();
8427                                self.write_keyword("BY DEFAULT");
8428                                if gen.on_null {
8429                                    self.write_space();
8430                                    self.write_keyword("ON NULL");
8431                                }
8432                            }
8433                            self.write_space();
8434                            self.write_keyword("AS IDENTITY");
8435
8436                            let has_options = gen.start.is_some()
8437                                || gen.increment.is_some()
8438                                || gen.minvalue.is_some()
8439                                || gen.maxvalue.is_some()
8440                                || gen.cycle.is_some();
8441                            if has_options {
8442                                self.write(" (");
8443                                let mut first = true;
8444                                if let Some(ref start) = gen.start {
8445                                    if !first {
8446                                        self.write(" ");
8447                                    }
8448                                    first = false;
8449                                    self.write_keyword("START WITH");
8450                                    self.write_space();
8451                                    self.generate_expression(start)?;
8452                                }
8453                                if let Some(ref incr) = gen.increment {
8454                                    if !first {
8455                                        self.write(" ");
8456                                    }
8457                                    first = false;
8458                                    self.write_keyword("INCREMENT BY");
8459                                    self.write_space();
8460                                    self.generate_expression(incr)?;
8461                                }
8462                                if let Some(ref minv) = gen.minvalue {
8463                                    if !first {
8464                                        self.write(" ");
8465                                    }
8466                                    first = false;
8467                                    self.write_keyword("MINVALUE");
8468                                    self.write_space();
8469                                    self.generate_expression(minv)?;
8470                                }
8471                                if let Some(ref maxv) = gen.maxvalue {
8472                                    if !first {
8473                                        self.write(" ");
8474                                    }
8475                                    first = false;
8476                                    self.write_keyword("MAXVALUE");
8477                                    self.write_space();
8478                                    self.generate_expression(maxv)?;
8479                                }
8480                                if let Some(cycle) = gen.cycle {
8481                                    if !first {
8482                                        self.write(" ");
8483                                    }
8484                                    if cycle {
8485                                        self.write_keyword("CYCLE");
8486                                    } else {
8487                                        self.write_keyword("NO CYCLE");
8488                                    }
8489                                }
8490                                self.write(")");
8491                            }
8492                        }
8493                    }
8494                    ColumnConstraint::Collate(collation) => {
8495                        self.write_space();
8496                        self.write_keyword("COLLATE");
8497                        self.write_space();
8498                        self.generate_identifier(collation)?;
8499                    }
8500                    ColumnConstraint::Comment(comment) => {
8501                        self.write_space();
8502                        self.write_keyword("COMMENT");
8503                        self.write_space();
8504                        self.generate_string_literal(comment)?;
8505                    }
8506                    ColumnConstraint::Path(path_expr) => {
8507                        self.write_space();
8508                        self.write_keyword("PATH");
8509                        self.write_space();
8510                        self.generate_expression(path_expr)?;
8511                    }
8512                    _ => {} // Other constraints handled above
8513                }
8514            }
8515
8516            // Redshift: ENCODE encoding_type (legacy path)
8517            if let Some(ref encoding) = col.encoding {
8518                self.write_space();
8519                self.write_keyword("ENCODE");
8520                self.write_space();
8521                self.write(encoding);
8522            }
8523        }
8524
8525        // ClickHouse: CODEC(...)
8526        if let Some(ref codec) = col.codec {
8527            self.write_space();
8528            self.write_keyword("CODEC");
8529            self.write("(");
8530            self.write(codec);
8531            self.write(")");
8532        }
8533
8534        // ClickHouse: EPHEMERAL [expr]
8535        if let Some(ref ephemeral) = col.ephemeral {
8536            self.write_space();
8537            self.write_keyword("EPHEMERAL");
8538            if let Some(ref expr) = ephemeral {
8539                self.write_space();
8540                self.generate_expression(expr)?;
8541            }
8542        }
8543
8544        // ClickHouse: MATERIALIZED expr
8545        if let Some(ref mat_expr) = col.materialized_expr {
8546            self.write_space();
8547            self.write_keyword("MATERIALIZED");
8548            self.write_space();
8549            self.generate_expression(mat_expr)?;
8550        }
8551
8552        // ClickHouse: ALIAS expr
8553        if let Some(ref alias_expr) = col.alias_expr {
8554            self.write_space();
8555            self.write_keyword("ALIAS");
8556            self.write_space();
8557            self.generate_expression(alias_expr)?;
8558        }
8559
8560        // ClickHouse: TTL expr
8561        if let Some(ref ttl_expr) = col.ttl_expr {
8562            self.write_space();
8563            self.write_keyword("TTL");
8564            self.write_space();
8565            self.generate_expression(ttl_expr)?;
8566        }
8567
8568        // TSQL: NOT FOR REPLICATION
8569        if col.not_for_replication
8570            && matches!(
8571                self.config.dialect,
8572                Some(crate::dialects::DialectType::TSQL)
8573                    | Some(crate::dialects::DialectType::Fabric)
8574            )
8575        {
8576            self.write_space();
8577            self.write_keyword("NOT FOR REPLICATION");
8578        }
8579
8580        // BigQuery: OPTIONS (key=value, ...) on column - comes after all constraints
8581        if !col.options.is_empty() {
8582            self.write_space();
8583            self.generate_options_clause(&col.options)?;
8584        }
8585
8586        // SQLite: Inline PRIMARY KEY from table constraint
8587        // This comes at the end, after all existing column constraints
8588        if !col.primary_key
8589            && self
8590                .sqlite_inline_pk_columns
8591                .contains(&col.name.name.to_lowercase())
8592        {
8593            self.write_space();
8594            self.write_keyword("PRIMARY KEY");
8595        }
8596
8597        // SERIAL expansion: add GENERATED BY DEFAULT AS IDENTITY NOT NULL for PostgreSQL,
8598        // just NOT NULL for Materialize (which strips GENERATED AS IDENTITY)
8599        if serial_expansion.is_some() {
8600            if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
8601                self.write_space();
8602                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY NOT NULL");
8603            } else if matches!(self.config.dialect, Some(DialectType::Materialize)) {
8604                self.write_space();
8605                self.write_keyword("NOT NULL");
8606            }
8607        }
8608
8609        Ok(())
8610    }
8611
8612    fn generate_table_constraint(&mut self, constraint: &TableConstraint) -> Result<()> {
8613        match constraint {
8614            TableConstraint::PrimaryKey {
8615                name,
8616                columns,
8617                include_columns,
8618                modifiers,
8619                has_constraint_keyword,
8620            } => {
8621                if let Some(ref n) = name {
8622                    if *has_constraint_keyword {
8623                        self.write_keyword("CONSTRAINT");
8624                        self.write_space();
8625                        self.generate_identifier(n)?;
8626                        self.write_space();
8627                    }
8628                }
8629                self.write_keyword("PRIMARY KEY");
8630                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
8631                if let Some(ref clustered) = modifiers.clustered {
8632                    self.write_space();
8633                    self.write_keyword(clustered);
8634                }
8635                // MySQL format: PRIMARY KEY name (cols) when no CONSTRAINT keyword
8636                if let Some(ref n) = name {
8637                    if !*has_constraint_keyword {
8638                        self.write_space();
8639                        self.generate_identifier(n)?;
8640                    }
8641                }
8642                self.write(" (");
8643                for (i, col) in columns.iter().enumerate() {
8644                    if i > 0 {
8645                        self.write(", ");
8646                    }
8647                    self.generate_identifier(col)?;
8648                }
8649                self.write(")");
8650                if !include_columns.is_empty() {
8651                    self.write_space();
8652                    self.write_keyword("INCLUDE");
8653                    self.write(" (");
8654                    for (i, col) in include_columns.iter().enumerate() {
8655                        if i > 0 {
8656                            self.write(", ");
8657                        }
8658                        self.generate_identifier(col)?;
8659                    }
8660                    self.write(")");
8661                }
8662                self.generate_constraint_modifiers(modifiers);
8663            }
8664            TableConstraint::Unique {
8665                name,
8666                columns,
8667                columns_parenthesized,
8668                modifiers,
8669                has_constraint_keyword,
8670                nulls_not_distinct,
8671            } => {
8672                if let Some(ref n) = name {
8673                    if *has_constraint_keyword {
8674                        self.write_keyword("CONSTRAINT");
8675                        self.write_space();
8676                        self.generate_identifier(n)?;
8677                        self.write_space();
8678                    }
8679                }
8680                self.write_keyword("UNIQUE");
8681                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
8682                if let Some(ref clustered) = modifiers.clustered {
8683                    self.write_space();
8684                    self.write_keyword(clustered);
8685                }
8686                // PostgreSQL 15+: NULLS NOT DISTINCT
8687                if *nulls_not_distinct {
8688                    self.write(" NULLS NOT DISTINCT");
8689                }
8690                // MySQL format: UNIQUE name (cols) when no CONSTRAINT keyword
8691                if let Some(ref n) = name {
8692                    if !*has_constraint_keyword {
8693                        self.write_space();
8694                        self.generate_identifier(n)?;
8695                    }
8696                }
8697                if *columns_parenthesized {
8698                    self.write(" (");
8699                    for (i, col) in columns.iter().enumerate() {
8700                        if i > 0 {
8701                            self.write(", ");
8702                        }
8703                        self.generate_identifier(col)?;
8704                    }
8705                    self.write(")");
8706                } else {
8707                    // UNIQUE without parentheses (e.g., UNIQUE idx_name)
8708                    for col in columns.iter() {
8709                        self.write_space();
8710                        self.generate_identifier(col)?;
8711                    }
8712                }
8713                self.generate_constraint_modifiers(modifiers);
8714            }
8715            TableConstraint::ForeignKey {
8716                name,
8717                columns,
8718                references,
8719                on_delete,
8720                on_update,
8721                modifiers,
8722            } => {
8723                if let Some(ref n) = name {
8724                    self.write_keyword("CONSTRAINT");
8725                    self.write_space();
8726                    self.generate_identifier(n)?;
8727                    self.write_space();
8728                }
8729                self.write_keyword("FOREIGN KEY");
8730                self.write(" (");
8731                for (i, col) in columns.iter().enumerate() {
8732                    if i > 0 {
8733                        self.write(", ");
8734                    }
8735                    self.generate_identifier(col)?;
8736                }
8737                self.write(")");
8738                if let Some(ref refs) = references {
8739                    self.write(" ");
8740                    self.write_keyword("REFERENCES");
8741                    self.write_space();
8742                    self.generate_table(&refs.table)?;
8743                    if !refs.columns.is_empty() {
8744                        if self.config.pretty {
8745                            self.write(" (");
8746                            self.write_newline();
8747                            self.indent_level += 1;
8748                            for (i, col) in refs.columns.iter().enumerate() {
8749                                if i > 0 {
8750                                    self.write(",");
8751                                    self.write_newline();
8752                                }
8753                                self.write_indent();
8754                                self.generate_identifier(col)?;
8755                            }
8756                            self.indent_level -= 1;
8757                            self.write_newline();
8758                            self.write_indent();
8759                            self.write(")");
8760                        } else {
8761                            self.write(" (");
8762                            for (i, col) in refs.columns.iter().enumerate() {
8763                                if i > 0 {
8764                                    self.write(", ");
8765                                }
8766                                self.generate_identifier(col)?;
8767                            }
8768                            self.write(")");
8769                        }
8770                    }
8771                    self.generate_referential_actions(refs)?;
8772                } else {
8773                    // No REFERENCES - output ON DELETE/ON UPDATE directly
8774                    if let Some(ref action) = on_delete {
8775                        self.write_space();
8776                        self.write_keyword("ON DELETE");
8777                        self.write_space();
8778                        self.generate_referential_action(action);
8779                    }
8780                    if let Some(ref action) = on_update {
8781                        self.write_space();
8782                        self.write_keyword("ON UPDATE");
8783                        self.write_space();
8784                        self.generate_referential_action(action);
8785                    }
8786                }
8787                self.generate_constraint_modifiers(modifiers);
8788            }
8789            TableConstraint::Check {
8790                name,
8791                expression,
8792                modifiers,
8793            } => {
8794                if let Some(ref n) = name {
8795                    self.write_keyword("CONSTRAINT");
8796                    self.write_space();
8797                    self.generate_identifier(n)?;
8798                    self.write_space();
8799                }
8800                self.write_keyword("CHECK");
8801                self.write(" (");
8802                self.generate_expression(expression)?;
8803                self.write(")");
8804                self.generate_constraint_modifiers(modifiers);
8805            }
8806            TableConstraint::Index {
8807                name,
8808                columns,
8809                kind,
8810                modifiers,
8811                use_key_keyword,
8812                expression,
8813                index_type,
8814                granularity,
8815            } => {
8816                // ClickHouse-style INDEX: INDEX name expr TYPE type_func GRANULARITY n
8817                if expression.is_some() {
8818                    self.write_keyword("INDEX");
8819                    if let Some(ref n) = name {
8820                        self.write_space();
8821                        self.generate_identifier(n)?;
8822                    }
8823                    if let Some(ref expr) = expression {
8824                        self.write_space();
8825                        self.generate_expression(expr)?;
8826                    }
8827                    if let Some(ref idx_type) = index_type {
8828                        self.write_space();
8829                        self.write_keyword("TYPE");
8830                        self.write_space();
8831                        self.generate_expression(idx_type)?;
8832                    }
8833                    if let Some(ref gran) = granularity {
8834                        self.write_space();
8835                        self.write_keyword("GRANULARITY");
8836                        self.write_space();
8837                        self.generate_expression(gran)?;
8838                    }
8839                } else {
8840                    // Standard INDEX syntax
8841                    // Determine the index keyword to use
8842                    // MySQL normalizes KEY to INDEX
8843                    use crate::dialects::DialectType;
8844                    let index_keyword = if *use_key_keyword
8845                        && !matches!(self.config.dialect, Some(DialectType::MySQL))
8846                    {
8847                        "KEY"
8848                    } else {
8849                        "INDEX"
8850                    };
8851
8852                    // Output kind (UNIQUE, FULLTEXT, SPATIAL) if present
8853                    if let Some(ref k) = kind {
8854                        self.write_keyword(k);
8855                        // For UNIQUE, don't add INDEX/KEY keyword
8856                        if k != "UNIQUE" {
8857                            self.write_space();
8858                            self.write_keyword(index_keyword);
8859                        }
8860                    } else {
8861                        self.write_keyword(index_keyword);
8862                    }
8863
8864                    // Output USING before name if using_before_columns is true and there's no name
8865                    if modifiers.using_before_columns && name.is_none() {
8866                        if let Some(ref using) = modifiers.using {
8867                            self.write_space();
8868                            self.write_keyword("USING");
8869                            self.write_space();
8870                            self.write_keyword(using);
8871                        }
8872                    }
8873
8874                    // Output index name if present
8875                    if let Some(ref n) = name {
8876                        self.write_space();
8877                        self.generate_identifier(n)?;
8878                    }
8879
8880                    // Output USING after name but before columns if using_before_columns and there's a name
8881                    if modifiers.using_before_columns && name.is_some() {
8882                        if let Some(ref using) = modifiers.using {
8883                            self.write_space();
8884                            self.write_keyword("USING");
8885                            self.write_space();
8886                            self.write_keyword(using);
8887                        }
8888                    }
8889
8890                    // Output columns
8891                    self.write(" (");
8892                    for (i, col) in columns.iter().enumerate() {
8893                        if i > 0 {
8894                            self.write(", ");
8895                        }
8896                        self.generate_identifier(col)?;
8897                    }
8898                    self.write(")");
8899
8900                    // Output USING after columns if not using_before_columns
8901                    if !modifiers.using_before_columns {
8902                        if let Some(ref using) = modifiers.using {
8903                            self.write_space();
8904                            self.write_keyword("USING");
8905                            self.write_space();
8906                            self.write_keyword(using);
8907                        }
8908                    }
8909
8910                    // Output other constraint modifiers (but skip USING since we already handled it)
8911                    self.generate_constraint_modifiers_without_using(modifiers);
8912                }
8913            }
8914            TableConstraint::Projection { name, expression } => {
8915                // ClickHouse: PROJECTION name (SELECT ...)
8916                self.write_keyword("PROJECTION");
8917                self.write_space();
8918                self.generate_identifier(name)?;
8919                self.write(" (");
8920                self.generate_expression(expression)?;
8921                self.write(")");
8922            }
8923            TableConstraint::Like { source, options } => {
8924                self.write_keyword("LIKE");
8925                self.write_space();
8926                self.generate_table(source)?;
8927                for (action, prop) in options {
8928                    self.write_space();
8929                    match action {
8930                        LikeOptionAction::Including => self.write_keyword("INCLUDING"),
8931                        LikeOptionAction::Excluding => self.write_keyword("EXCLUDING"),
8932                    }
8933                    self.write_space();
8934                    self.write_keyword(prop);
8935                }
8936            }
8937            TableConstraint::PeriodForSystemTime { start_col, end_col } => {
8938                self.write_keyword("PERIOD FOR SYSTEM_TIME");
8939                self.write(" (");
8940                self.generate_identifier(start_col)?;
8941                self.write(", ");
8942                self.generate_identifier(end_col)?;
8943                self.write(")");
8944            }
8945            TableConstraint::Exclude {
8946                name,
8947                using,
8948                elements,
8949                include_columns,
8950                where_clause,
8951                with_params,
8952                using_index_tablespace,
8953                modifiers: _,
8954            } => {
8955                if let Some(ref n) = name {
8956                    self.write_keyword("CONSTRAINT");
8957                    self.write_space();
8958                    self.generate_identifier(n)?;
8959                    self.write_space();
8960                }
8961                self.write_keyword("EXCLUDE");
8962                if let Some(ref method) = using {
8963                    self.write_space();
8964                    self.write_keyword("USING");
8965                    self.write_space();
8966                    self.write(method);
8967                    self.write("(");
8968                } else {
8969                    self.write(" (");
8970                }
8971                for (i, elem) in elements.iter().enumerate() {
8972                    if i > 0 {
8973                        self.write(", ");
8974                    }
8975                    self.write(&elem.expression);
8976                    self.write_space();
8977                    self.write_keyword("WITH");
8978                    self.write_space();
8979                    self.write(&elem.operator);
8980                }
8981                self.write(")");
8982                if !include_columns.is_empty() {
8983                    self.write_space();
8984                    self.write_keyword("INCLUDE");
8985                    self.write(" (");
8986                    for (i, col) in include_columns.iter().enumerate() {
8987                        if i > 0 {
8988                            self.write(", ");
8989                        }
8990                        self.generate_identifier(col)?;
8991                    }
8992                    self.write(")");
8993                }
8994                if !with_params.is_empty() {
8995                    self.write_space();
8996                    self.write_keyword("WITH");
8997                    self.write(" (");
8998                    for (i, (key, val)) in with_params.iter().enumerate() {
8999                        if i > 0 {
9000                            self.write(", ");
9001                        }
9002                        self.write(key);
9003                        self.write("=");
9004                        self.write(val);
9005                    }
9006                    self.write(")");
9007                }
9008                if let Some(ref tablespace) = using_index_tablespace {
9009                    self.write_space();
9010                    self.write_keyword("USING INDEX TABLESPACE");
9011                    self.write_space();
9012                    self.write(tablespace);
9013                }
9014                if let Some(ref where_expr) = where_clause {
9015                    self.write_space();
9016                    self.write_keyword("WHERE");
9017                    self.write(" (");
9018                    self.generate_expression(where_expr)?;
9019                    self.write(")");
9020                }
9021            }
9022            TableConstraint::Tags(tags) => {
9023                self.write_keyword("TAG");
9024                self.write(" (");
9025                for (i, expr) in tags.expressions.iter().enumerate() {
9026                    if i > 0 {
9027                        self.write(", ");
9028                    }
9029                    self.generate_expression(expr)?;
9030                }
9031                self.write(")");
9032            }
9033            TableConstraint::InitiallyDeferred { deferred } => {
9034                self.write_keyword("INITIALLY");
9035                self.write_space();
9036                if *deferred {
9037                    self.write_keyword("DEFERRED");
9038                } else {
9039                    self.write_keyword("IMMEDIATE");
9040                }
9041            }
9042        }
9043        Ok(())
9044    }
9045
9046    fn generate_constraint_modifiers(&mut self, modifiers: &ConstraintModifiers) {
9047        // Output USING BTREE/HASH (MySQL) - comes first
9048        if let Some(using) = &modifiers.using {
9049            self.write_space();
9050            self.write_keyword("USING");
9051            self.write_space();
9052            self.write_keyword(using);
9053        }
9054        // Output ENFORCED/NOT ENFORCED
9055        if let Some(enforced) = modifiers.enforced {
9056            self.write_space();
9057            if enforced {
9058                self.write_keyword("ENFORCED");
9059            } else {
9060                self.write_keyword("NOT ENFORCED");
9061            }
9062        }
9063        // Output DEFERRABLE/NOT DEFERRABLE
9064        if let Some(deferrable) = modifiers.deferrable {
9065            self.write_space();
9066            if deferrable {
9067                self.write_keyword("DEFERRABLE");
9068            } else {
9069                self.write_keyword("NOT DEFERRABLE");
9070            }
9071        }
9072        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
9073        if let Some(initially_deferred) = modifiers.initially_deferred {
9074            self.write_space();
9075            if initially_deferred {
9076                self.write_keyword("INITIALLY DEFERRED");
9077            } else {
9078                self.write_keyword("INITIALLY IMMEDIATE");
9079            }
9080        }
9081        // Output NORELY
9082        if modifiers.norely {
9083            self.write_space();
9084            self.write_keyword("NORELY");
9085        }
9086        // Output RELY
9087        if modifiers.rely {
9088            self.write_space();
9089            self.write_keyword("RELY");
9090        }
9091        // Output NOT VALID (PostgreSQL)
9092        if modifiers.not_valid {
9093            self.write_space();
9094            self.write_keyword("NOT VALID");
9095        }
9096        // Output ON CONFLICT (SQLite)
9097        if let Some(on_conflict) = &modifiers.on_conflict {
9098            self.write_space();
9099            self.write_keyword("ON CONFLICT");
9100            self.write_space();
9101            self.write_keyword(on_conflict);
9102        }
9103        // Output TSQL WITH options (PAD_INDEX=ON, STATISTICS_NORECOMPUTE=OFF, ...)
9104        if !modifiers.with_options.is_empty() {
9105            self.write_space();
9106            self.write_keyword("WITH");
9107            self.write(" (");
9108            for (i, (key, value)) in modifiers.with_options.iter().enumerate() {
9109                if i > 0 {
9110                    self.write(", ");
9111                }
9112                self.write(key);
9113                self.write("=");
9114                self.write(value);
9115            }
9116            self.write(")");
9117        }
9118        // Output TSQL ON filegroup
9119        if let Some(ref fg) = modifiers.on_filegroup {
9120            self.write_space();
9121            self.write_keyword("ON");
9122            self.write_space();
9123            let _ = self.generate_identifier(fg);
9124        }
9125    }
9126
9127    /// Generate constraint modifiers without USING (for Index constraints where USING is handled separately)
9128    fn generate_constraint_modifiers_without_using(&mut self, modifiers: &ConstraintModifiers) {
9129        // Output ENFORCED/NOT ENFORCED
9130        if let Some(enforced) = modifiers.enforced {
9131            self.write_space();
9132            if enforced {
9133                self.write_keyword("ENFORCED");
9134            } else {
9135                self.write_keyword("NOT ENFORCED");
9136            }
9137        }
9138        // Output DEFERRABLE/NOT DEFERRABLE
9139        if let Some(deferrable) = modifiers.deferrable {
9140            self.write_space();
9141            if deferrable {
9142                self.write_keyword("DEFERRABLE");
9143            } else {
9144                self.write_keyword("NOT DEFERRABLE");
9145            }
9146        }
9147        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
9148        if let Some(initially_deferred) = modifiers.initially_deferred {
9149            self.write_space();
9150            if initially_deferred {
9151                self.write_keyword("INITIALLY DEFERRED");
9152            } else {
9153                self.write_keyword("INITIALLY IMMEDIATE");
9154            }
9155        }
9156        // Output NORELY
9157        if modifiers.norely {
9158            self.write_space();
9159            self.write_keyword("NORELY");
9160        }
9161        // Output RELY
9162        if modifiers.rely {
9163            self.write_space();
9164            self.write_keyword("RELY");
9165        }
9166        // Output NOT VALID (PostgreSQL)
9167        if modifiers.not_valid {
9168            self.write_space();
9169            self.write_keyword("NOT VALID");
9170        }
9171        // Output ON CONFLICT (SQLite)
9172        if let Some(on_conflict) = &modifiers.on_conflict {
9173            self.write_space();
9174            self.write_keyword("ON CONFLICT");
9175            self.write_space();
9176            self.write_keyword(on_conflict);
9177        }
9178        // Output MySQL index-specific modifiers
9179        self.generate_index_specific_modifiers(modifiers);
9180    }
9181
9182    /// Generate MySQL index-specific modifiers (COMMENT, VISIBLE, ENGINE_ATTRIBUTE, WITH PARSER)
9183    fn generate_index_specific_modifiers(&mut self, modifiers: &ConstraintModifiers) {
9184        if let Some(ref comment) = modifiers.comment {
9185            self.write_space();
9186            self.write_keyword("COMMENT");
9187            self.write(" '");
9188            self.write(comment);
9189            self.write("'");
9190        }
9191        if let Some(visible) = modifiers.visible {
9192            self.write_space();
9193            if visible {
9194                self.write_keyword("VISIBLE");
9195            } else {
9196                self.write_keyword("INVISIBLE");
9197            }
9198        }
9199        if let Some(ref attr) = modifiers.engine_attribute {
9200            self.write_space();
9201            self.write_keyword("ENGINE_ATTRIBUTE");
9202            self.write(" = '");
9203            self.write(attr);
9204            self.write("'");
9205        }
9206        if let Some(ref parser) = modifiers.with_parser {
9207            self.write_space();
9208            self.write_keyword("WITH PARSER");
9209            self.write_space();
9210            self.write(parser);
9211        }
9212    }
9213
9214    fn generate_referential_actions(&mut self, fk_ref: &ForeignKeyRef) -> Result<()> {
9215        // MATCH clause before ON DELETE/ON UPDATE (default position, e.g. PostgreSQL)
9216        if !fk_ref.match_after_actions {
9217            if let Some(ref match_type) = fk_ref.match_type {
9218                self.write_space();
9219                self.write_keyword("MATCH");
9220                self.write_space();
9221                match match_type {
9222                    MatchType::Full => self.write_keyword("FULL"),
9223                    MatchType::Partial => self.write_keyword("PARTIAL"),
9224                    MatchType::Simple => self.write_keyword("SIMPLE"),
9225                }
9226            }
9227        }
9228
9229        // Output ON UPDATE and ON DELETE in the original order
9230        if fk_ref.on_update_first {
9231            if let Some(ref action) = fk_ref.on_update {
9232                self.write_space();
9233                self.write_keyword("ON UPDATE");
9234                self.write_space();
9235                self.generate_referential_action(action);
9236            }
9237            if let Some(ref action) = fk_ref.on_delete {
9238                self.write_space();
9239                self.write_keyword("ON DELETE");
9240                self.write_space();
9241                self.generate_referential_action(action);
9242            }
9243        } else {
9244            if let Some(ref action) = fk_ref.on_delete {
9245                self.write_space();
9246                self.write_keyword("ON DELETE");
9247                self.write_space();
9248                self.generate_referential_action(action);
9249            }
9250            if let Some(ref action) = fk_ref.on_update {
9251                self.write_space();
9252                self.write_keyword("ON UPDATE");
9253                self.write_space();
9254                self.generate_referential_action(action);
9255            }
9256        }
9257
9258        // MATCH clause after ON DELETE/ON UPDATE (when original SQL had it after)
9259        if fk_ref.match_after_actions {
9260            if let Some(ref match_type) = fk_ref.match_type {
9261                self.write_space();
9262                self.write_keyword("MATCH");
9263                self.write_space();
9264                match match_type {
9265                    MatchType::Full => self.write_keyword("FULL"),
9266                    MatchType::Partial => self.write_keyword("PARTIAL"),
9267                    MatchType::Simple => self.write_keyword("SIMPLE"),
9268                }
9269            }
9270        }
9271
9272        // DEFERRABLE / NOT DEFERRABLE
9273        if let Some(deferrable) = fk_ref.deferrable {
9274            self.write_space();
9275            if deferrable {
9276                self.write_keyword("DEFERRABLE");
9277            } else {
9278                self.write_keyword("NOT DEFERRABLE");
9279            }
9280        }
9281
9282        Ok(())
9283    }
9284
9285    fn generate_referential_action(&mut self, action: &ReferentialAction) {
9286        match action {
9287            ReferentialAction::Cascade => self.write_keyword("CASCADE"),
9288            ReferentialAction::SetNull => self.write_keyword("SET NULL"),
9289            ReferentialAction::SetDefault => self.write_keyword("SET DEFAULT"),
9290            ReferentialAction::Restrict => self.write_keyword("RESTRICT"),
9291            ReferentialAction::NoAction => self.write_keyword("NO ACTION"),
9292        }
9293    }
9294
9295    fn generate_drop_table(&mut self, dt: &DropTable) -> Result<()> {
9296        // Athena: DROP TABLE uses Hive engine (backticks)
9297        let saved_athena_hive_context = self.athena_hive_context;
9298        if matches!(
9299            self.config.dialect,
9300            Some(crate::dialects::DialectType::Athena)
9301        ) {
9302            self.athena_hive_context = true;
9303        }
9304
9305        // Output leading comments (e.g., "-- comment\nDROP TABLE ...")
9306        for comment in &dt.leading_comments {
9307            self.write_formatted_comment(comment);
9308            self.write_space();
9309        }
9310        self.write_keyword("DROP TABLE");
9311
9312        if dt.if_exists {
9313            self.write_space();
9314            self.write_keyword("IF EXISTS");
9315        }
9316
9317        self.write_space();
9318        for (i, table) in dt.names.iter().enumerate() {
9319            if i > 0 {
9320                self.write(", ");
9321            }
9322            self.generate_table(table)?;
9323        }
9324
9325        if dt.cascade_constraints {
9326            self.write_space();
9327            self.write_keyword("CASCADE CONSTRAINTS");
9328        } else if dt.cascade {
9329            self.write_space();
9330            self.write_keyword("CASCADE");
9331        }
9332
9333        if dt.purge {
9334            self.write_space();
9335            self.write_keyword("PURGE");
9336        }
9337
9338        // Restore Athena Hive context
9339        self.athena_hive_context = saved_athena_hive_context;
9340
9341        Ok(())
9342    }
9343
9344    fn generate_alter_table(&mut self, at: &AlterTable) -> Result<()> {
9345        // Athena: ALTER TABLE uses Hive engine (backticks)
9346        let saved_athena_hive_context = self.athena_hive_context;
9347        if matches!(
9348            self.config.dialect,
9349            Some(crate::dialects::DialectType::Athena)
9350        ) {
9351            self.athena_hive_context = true;
9352        }
9353
9354        self.write_keyword("ALTER TABLE");
9355        if at.if_exists {
9356            self.write_space();
9357            self.write_keyword("IF EXISTS");
9358        }
9359        self.write_space();
9360        self.generate_table(&at.name)?;
9361
9362        // ClickHouse: ON CLUSTER clause
9363        if let Some(ref on_cluster) = at.on_cluster {
9364            self.write_space();
9365            self.generate_on_cluster(on_cluster)?;
9366        }
9367
9368        // Hive: PARTITION(key=value, ...) clause
9369        if let Some(ref partition) = at.partition {
9370            self.write_space();
9371            self.write_keyword("PARTITION");
9372            self.write("(");
9373            for (i, (key, value)) in partition.iter().enumerate() {
9374                if i > 0 {
9375                    self.write(", ");
9376                }
9377                self.generate_identifier(key)?;
9378                self.write(" = ");
9379                self.generate_expression(value)?;
9380            }
9381            self.write(")");
9382        }
9383
9384        // TSQL: WITH CHECK / WITH NOCHECK modifier
9385        if let Some(ref with_check) = at.with_check {
9386            self.write_space();
9387            self.write_keyword(with_check);
9388        }
9389
9390        if self.config.pretty {
9391            // In pretty mode, format actions with newlines and indentation
9392            self.write_newline();
9393            self.indent_level += 1;
9394            for (i, action) in at.actions.iter().enumerate() {
9395                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
9396                let is_continuation = i > 0
9397                    && matches!(
9398                        (&at.actions[i - 1], action),
9399                        (
9400                            AlterTableAction::AddColumn { .. },
9401                            AlterTableAction::AddColumn { .. }
9402                        ) | (
9403                            AlterTableAction::AddConstraint(_),
9404                            AlterTableAction::AddConstraint(_)
9405                        )
9406                    );
9407                if i > 0 {
9408                    self.write(",");
9409                    self.write_newline();
9410                }
9411                self.write_indent();
9412                self.generate_alter_action_with_continuation(action, is_continuation)?;
9413            }
9414            self.indent_level -= 1;
9415        } else {
9416            for (i, action) in at.actions.iter().enumerate() {
9417                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
9418                let is_continuation = i > 0
9419                    && matches!(
9420                        (&at.actions[i - 1], action),
9421                        (
9422                            AlterTableAction::AddColumn { .. },
9423                            AlterTableAction::AddColumn { .. }
9424                        ) | (
9425                            AlterTableAction::AddConstraint(_),
9426                            AlterTableAction::AddConstraint(_)
9427                        )
9428                    );
9429                if i > 0 {
9430                    self.write(",");
9431                }
9432                self.write_space();
9433                self.generate_alter_action_with_continuation(action, is_continuation)?;
9434            }
9435        }
9436
9437        // MySQL ALTER TABLE trailing options
9438        if let Some(ref algorithm) = at.algorithm {
9439            self.write(", ");
9440            self.write_keyword("ALGORITHM");
9441            self.write("=");
9442            self.write_keyword(algorithm);
9443        }
9444        if let Some(ref lock) = at.lock {
9445            self.write(", ");
9446            self.write_keyword("LOCK");
9447            self.write("=");
9448            self.write_keyword(lock);
9449        }
9450
9451        // Restore Athena Hive context
9452        self.athena_hive_context = saved_athena_hive_context;
9453
9454        Ok(())
9455    }
9456
9457    fn generate_alter_action_with_continuation(
9458        &mut self,
9459        action: &AlterTableAction,
9460        is_continuation: bool,
9461    ) -> Result<()> {
9462        match action {
9463            AlterTableAction::AddColumn {
9464                column,
9465                if_not_exists,
9466                position,
9467            } => {
9468                use crate::dialects::DialectType;
9469                // For Snowflake: consecutive ADD COLUMN actions are combined with commas
9470                // e.g., "ADD col1, col2" instead of "ADD col1, ADD col2"
9471                // For other dialects, repeat ADD COLUMN for each
9472                let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
9473                let is_tsql_like = matches!(
9474                    self.config.dialect,
9475                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
9476                );
9477                // Athena uses "ADD COLUMNS (col_def)" instead of "ADD COLUMN col_def"
9478                let is_athena = matches!(self.config.dialect, Some(DialectType::Athena));
9479
9480                if is_continuation && (is_snowflake || is_tsql_like) {
9481                    // Don't write ADD keyword for continuation in Snowflake/TSQL
9482                } else if is_snowflake {
9483                    self.write_keyword("ADD");
9484                    self.write_space();
9485                } else if is_athena {
9486                    // Athena uses ADD COLUMNS (col_def) syntax
9487                    self.write_keyword("ADD COLUMNS");
9488                    self.write(" (");
9489                } else if self.config.alter_table_include_column_keyword {
9490                    self.write_keyword("ADD COLUMN");
9491                    self.write_space();
9492                } else {
9493                    // Dialects like Oracle and TSQL don't use COLUMN keyword
9494                    self.write_keyword("ADD");
9495                    self.write_space();
9496                }
9497
9498                if *if_not_exists {
9499                    self.write_keyword("IF NOT EXISTS");
9500                    self.write_space();
9501                }
9502                self.generate_column_def(column)?;
9503
9504                // Close parenthesis for Athena
9505                if is_athena {
9506                    self.write(")");
9507                }
9508
9509                // Column position (FIRST or AFTER)
9510                if let Some(pos) = position {
9511                    self.write_space();
9512                    match pos {
9513                        ColumnPosition::First => self.write_keyword("FIRST"),
9514                        ColumnPosition::After(col_name) => {
9515                            self.write_keyword("AFTER");
9516                            self.write_space();
9517                            self.generate_identifier(col_name)?;
9518                        }
9519                    }
9520                }
9521            }
9522            AlterTableAction::DropColumn {
9523                name,
9524                if_exists,
9525                cascade,
9526            } => {
9527                self.write_keyword("DROP COLUMN");
9528                if *if_exists {
9529                    self.write_space();
9530                    self.write_keyword("IF EXISTS");
9531                }
9532                self.write_space();
9533                self.generate_identifier(name)?;
9534                if *cascade {
9535                    self.write_space();
9536                    self.write_keyword("CASCADE");
9537                }
9538            }
9539            AlterTableAction::DropColumns { names } => {
9540                self.write_keyword("DROP COLUMNS");
9541                self.write(" (");
9542                for (i, name) in names.iter().enumerate() {
9543                    if i > 0 {
9544                        self.write(", ");
9545                    }
9546                    self.generate_identifier(name)?;
9547                }
9548                self.write(")");
9549            }
9550            AlterTableAction::RenameColumn {
9551                old_name,
9552                new_name,
9553                if_exists,
9554            } => {
9555                self.write_keyword("RENAME COLUMN");
9556                if *if_exists {
9557                    self.write_space();
9558                    self.write_keyword("IF EXISTS");
9559                }
9560                self.write_space();
9561                self.generate_identifier(old_name)?;
9562                self.write_space();
9563                self.write_keyword("TO");
9564                self.write_space();
9565                self.generate_identifier(new_name)?;
9566            }
9567            AlterTableAction::AlterColumn {
9568                name,
9569                action,
9570                use_modify_keyword,
9571            } => {
9572                use crate::dialects::DialectType;
9573                // MySQL uses MODIFY COLUMN for type changes (SetDataType)
9574                // but ALTER COLUMN for SET DEFAULT, DROP DEFAULT, etc.
9575                let use_modify = *use_modify_keyword
9576                    || (matches!(self.config.dialect, Some(DialectType::MySQL))
9577                        && matches!(action, AlterColumnAction::SetDataType { .. }));
9578                if use_modify {
9579                    self.write_keyword("MODIFY COLUMN");
9580                    self.write_space();
9581                    self.generate_identifier(name)?;
9582                    // For MODIFY COLUMN, output the type directly
9583                    if let AlterColumnAction::SetDataType {
9584                        data_type,
9585                        using: _,
9586                        collate,
9587                    } = action
9588                    {
9589                        self.write_space();
9590                        self.generate_data_type(data_type)?;
9591                        // Output COLLATE clause if present
9592                        if let Some(collate_name) = collate {
9593                            self.write_space();
9594                            self.write_keyword("COLLATE");
9595                            self.write_space();
9596                            // Output as single-quoted string
9597                            self.write(&format!("'{}'", collate_name));
9598                        }
9599                    } else {
9600                        self.write_space();
9601                        self.generate_alter_column_action(action)?;
9602                    }
9603                } else if matches!(self.config.dialect, Some(DialectType::Hive))
9604                    && matches!(action, AlterColumnAction::SetDataType { .. })
9605                {
9606                    // Hive uses CHANGE COLUMN col_name col_name NEW_TYPE
9607                    self.write_keyword("CHANGE COLUMN");
9608                    self.write_space();
9609                    self.generate_identifier(name)?;
9610                    self.write_space();
9611                    self.generate_identifier(name)?;
9612                    if let AlterColumnAction::SetDataType { data_type, .. } = action {
9613                        self.write_space();
9614                        self.generate_data_type(data_type)?;
9615                    }
9616                } else {
9617                    self.write_keyword("ALTER COLUMN");
9618                    self.write_space();
9619                    self.generate_identifier(name)?;
9620                    self.write_space();
9621                    self.generate_alter_column_action(action)?;
9622                }
9623            }
9624            AlterTableAction::RenameTable(new_name) => {
9625                // MySQL-like dialects (MySQL, Doris, StarRocks) use RENAME without TO
9626                let mysql_like = matches!(
9627                    self.config.dialect,
9628                    Some(DialectType::MySQL)
9629                        | Some(DialectType::Doris)
9630                        | Some(DialectType::StarRocks)
9631                        | Some(DialectType::SingleStore)
9632                );
9633                if mysql_like {
9634                    self.write_keyword("RENAME");
9635                } else {
9636                    self.write_keyword("RENAME TO");
9637                }
9638                self.write_space();
9639                // Doris, DuckDB, BigQuery, PostgreSQL strip schema/catalog from target table
9640                let rename_table_with_db = !matches!(
9641                    self.config.dialect,
9642                    Some(DialectType::Doris)
9643                        | Some(DialectType::DuckDB)
9644                        | Some(DialectType::BigQuery)
9645                        | Some(DialectType::PostgreSQL)
9646                );
9647                if !rename_table_with_db {
9648                    let mut stripped = new_name.clone();
9649                    stripped.schema = None;
9650                    stripped.catalog = None;
9651                    self.generate_table(&stripped)?;
9652                } else {
9653                    self.generate_table(new_name)?;
9654                }
9655            }
9656            AlterTableAction::AddConstraint(constraint) => {
9657                // For consecutive ADD CONSTRAINT actions (is_continuation=true), skip ADD keyword
9658                // to produce: ADD CONSTRAINT c1 ..., CONSTRAINT c2 ...
9659                if !is_continuation {
9660                    self.write_keyword("ADD");
9661                    self.write_space();
9662                }
9663                self.generate_table_constraint(constraint)?;
9664            }
9665            AlterTableAction::DropConstraint { name, if_exists } => {
9666                self.write_keyword("DROP CONSTRAINT");
9667                if *if_exists {
9668                    self.write_space();
9669                    self.write_keyword("IF EXISTS");
9670                }
9671                self.write_space();
9672                self.generate_identifier(name)?;
9673            }
9674            AlterTableAction::DropForeignKey { name } => {
9675                self.write_keyword("DROP FOREIGN KEY");
9676                self.write_space();
9677                self.generate_identifier(name)?;
9678            }
9679            AlterTableAction::DropPartition {
9680                partitions,
9681                if_exists,
9682            } => {
9683                self.write_keyword("DROP");
9684                if *if_exists {
9685                    self.write_space();
9686                    self.write_keyword("IF EXISTS");
9687                }
9688                for (i, partition) in partitions.iter().enumerate() {
9689                    if i > 0 {
9690                        self.write(",");
9691                    }
9692                    self.write_space();
9693                    self.write_keyword("PARTITION");
9694                    // Check for special ClickHouse partition formats
9695                    if partition.len() == 1 && partition[0].0.name == "__expr__" {
9696                        // ClickHouse: PARTITION <expression>
9697                        self.write_space();
9698                        self.generate_expression(&partition[0].1)?;
9699                    } else if partition.len() == 1 && partition[0].0.name == "ALL" {
9700                        // ClickHouse: PARTITION ALL
9701                        self.write_space();
9702                        self.write_keyword("ALL");
9703                    } else if partition.len() == 1 && partition[0].0.name == "ID" {
9704                        // ClickHouse: PARTITION ID 'string'
9705                        self.write_space();
9706                        self.write_keyword("ID");
9707                        self.write_space();
9708                        self.generate_expression(&partition[0].1)?;
9709                    } else {
9710                        // Standard SQL: PARTITION(key=value, ...)
9711                        self.write("(");
9712                        for (j, (key, value)) in partition.iter().enumerate() {
9713                            if j > 0 {
9714                                self.write(", ");
9715                            }
9716                            self.generate_identifier(key)?;
9717                            self.write(" = ");
9718                            self.generate_expression(value)?;
9719                        }
9720                        self.write(")");
9721                    }
9722                }
9723            }
9724            AlterTableAction::Delete { where_clause } => {
9725                self.write_keyword("DELETE");
9726                self.write_space();
9727                self.write_keyword("WHERE");
9728                self.write_space();
9729                self.generate_expression(where_clause)?;
9730            }
9731            AlterTableAction::SwapWith(target) => {
9732                self.write_keyword("SWAP WITH");
9733                self.write_space();
9734                self.generate_table(target)?;
9735            }
9736            AlterTableAction::SetProperty { properties } => {
9737                use crate::dialects::DialectType;
9738                self.write_keyword("SET");
9739                // Trino/Presto use SET PROPERTIES syntax with spaces around =
9740                let is_trino_presto = matches!(
9741                    self.config.dialect,
9742                    Some(DialectType::Trino) | Some(DialectType::Presto)
9743                );
9744                if is_trino_presto {
9745                    self.write_space();
9746                    self.write_keyword("PROPERTIES");
9747                }
9748                let eq = if is_trino_presto { " = " } else { "=" };
9749                for (i, (key, value)) in properties.iter().enumerate() {
9750                    if i > 0 {
9751                        self.write(",");
9752                    }
9753                    self.write_space();
9754                    // Handle quoted property names for Trino
9755                    if key.contains(' ') {
9756                        self.generate_string_literal(key)?;
9757                    } else {
9758                        self.write(key);
9759                    }
9760                    self.write(eq);
9761                    self.generate_expression(value)?;
9762                }
9763            }
9764            AlterTableAction::UnsetProperty { properties } => {
9765                self.write_keyword("UNSET");
9766                for (i, name) in properties.iter().enumerate() {
9767                    if i > 0 {
9768                        self.write(",");
9769                    }
9770                    self.write_space();
9771                    self.write(name);
9772                }
9773            }
9774            AlterTableAction::ClusterBy { expressions } => {
9775                self.write_keyword("CLUSTER BY");
9776                self.write(" (");
9777                for (i, expr) in expressions.iter().enumerate() {
9778                    if i > 0 {
9779                        self.write(", ");
9780                    }
9781                    self.generate_expression(expr)?;
9782                }
9783                self.write(")");
9784            }
9785            AlterTableAction::SetTag { expressions } => {
9786                self.write_keyword("SET TAG");
9787                for (i, (key, value)) in expressions.iter().enumerate() {
9788                    if i > 0 {
9789                        self.write(",");
9790                    }
9791                    self.write_space();
9792                    self.write(key);
9793                    self.write(" = ");
9794                    self.generate_expression(value)?;
9795                }
9796            }
9797            AlterTableAction::UnsetTag { names } => {
9798                self.write_keyword("UNSET TAG");
9799                for (i, name) in names.iter().enumerate() {
9800                    if i > 0 {
9801                        self.write(",");
9802                    }
9803                    self.write_space();
9804                    self.write(name);
9805                }
9806            }
9807            AlterTableAction::SetOptions { expressions } => {
9808                self.write_keyword("SET");
9809                self.write(" (");
9810                for (i, expr) in expressions.iter().enumerate() {
9811                    if i > 0 {
9812                        self.write(", ");
9813                    }
9814                    self.generate_expression(expr)?;
9815                }
9816                self.write(")");
9817            }
9818            AlterTableAction::AlterIndex { name, visible } => {
9819                self.write_keyword("ALTER INDEX");
9820                self.write_space();
9821                self.generate_identifier(name)?;
9822                self.write_space();
9823                if *visible {
9824                    self.write_keyword("VISIBLE");
9825                } else {
9826                    self.write_keyword("INVISIBLE");
9827                }
9828            }
9829            AlterTableAction::SetAttribute { attribute } => {
9830                self.write_keyword("SET");
9831                self.write_space();
9832                self.write_keyword(attribute);
9833            }
9834            AlterTableAction::SetStageFileFormat { options } => {
9835                self.write_keyword("SET");
9836                self.write_space();
9837                self.write_keyword("STAGE_FILE_FORMAT");
9838                self.write(" = (");
9839                if let Some(opts) = options {
9840                    self.generate_space_separated_properties(opts)?;
9841                }
9842                self.write(")");
9843            }
9844            AlterTableAction::SetStageCopyOptions { options } => {
9845                self.write_keyword("SET");
9846                self.write_space();
9847                self.write_keyword("STAGE_COPY_OPTIONS");
9848                self.write(" = (");
9849                if let Some(opts) = options {
9850                    self.generate_space_separated_properties(opts)?;
9851                }
9852                self.write(")");
9853            }
9854            AlterTableAction::AddColumns { columns, cascade } => {
9855                // Oracle uses ADD (...) without COLUMNS keyword
9856                // Hive/Spark uses ADD COLUMNS (...)
9857                let is_oracle = matches!(self.config.dialect, Some(DialectType::Oracle));
9858                if is_oracle {
9859                    self.write_keyword("ADD");
9860                } else {
9861                    self.write_keyword("ADD COLUMNS");
9862                }
9863                self.write(" (");
9864                for (i, col) in columns.iter().enumerate() {
9865                    if i > 0 {
9866                        self.write(", ");
9867                    }
9868                    self.generate_column_def(col)?;
9869                }
9870                self.write(")");
9871                if *cascade {
9872                    self.write_space();
9873                    self.write_keyword("CASCADE");
9874                }
9875            }
9876            AlterTableAction::ChangeColumn {
9877                old_name,
9878                new_name,
9879                data_type,
9880                comment,
9881                cascade,
9882            } => {
9883                use crate::dialects::DialectType;
9884                let is_spark = matches!(
9885                    self.config.dialect,
9886                    Some(DialectType::Spark) | Some(DialectType::Databricks)
9887                );
9888                let is_rename = old_name.name != new_name.name;
9889
9890                if is_spark {
9891                    if is_rename {
9892                        // Spark: RENAME COLUMN old TO new
9893                        self.write_keyword("RENAME COLUMN");
9894                        self.write_space();
9895                        self.generate_identifier(old_name)?;
9896                        self.write_space();
9897                        self.write_keyword("TO");
9898                        self.write_space();
9899                        self.generate_identifier(new_name)?;
9900                    } else if comment.is_some() {
9901                        // Spark: ALTER COLUMN old COMMENT 'comment'
9902                        self.write_keyword("ALTER COLUMN");
9903                        self.write_space();
9904                        self.generate_identifier(old_name)?;
9905                        self.write_space();
9906                        self.write_keyword("COMMENT");
9907                        self.write_space();
9908                        self.write("'");
9909                        self.write(comment.as_ref().unwrap());
9910                        self.write("'");
9911                    } else if data_type.is_some() {
9912                        // Spark: ALTER COLUMN old TYPE data_type
9913                        self.write_keyword("ALTER COLUMN");
9914                        self.write_space();
9915                        self.generate_identifier(old_name)?;
9916                        self.write_space();
9917                        self.write_keyword("TYPE");
9918                        self.write_space();
9919                        self.generate_data_type(data_type.as_ref().unwrap())?;
9920                    } else {
9921                        // Fallback to CHANGE COLUMN
9922                        self.write_keyword("CHANGE COLUMN");
9923                        self.write_space();
9924                        self.generate_identifier(old_name)?;
9925                        self.write_space();
9926                        self.generate_identifier(new_name)?;
9927                    }
9928                } else {
9929                    // Hive/MySQL/default: CHANGE [COLUMN] old new [type] [COMMENT '...'] [CASCADE]
9930                    if data_type.is_some() {
9931                        self.write_keyword("CHANGE COLUMN");
9932                    } else {
9933                        self.write_keyword("CHANGE");
9934                    }
9935                    self.write_space();
9936                    self.generate_identifier(old_name)?;
9937                    self.write_space();
9938                    self.generate_identifier(new_name)?;
9939                    if let Some(ref dt) = data_type {
9940                        self.write_space();
9941                        self.generate_data_type(dt)?;
9942                    }
9943                    if let Some(ref c) = comment {
9944                        self.write_space();
9945                        self.write_keyword("COMMENT");
9946                        self.write_space();
9947                        self.write("'");
9948                        self.write(c);
9949                        self.write("'");
9950                    }
9951                    if *cascade {
9952                        self.write_space();
9953                        self.write_keyword("CASCADE");
9954                    }
9955                }
9956            }
9957            AlterTableAction::AddPartition {
9958                partition,
9959                if_not_exists,
9960                location,
9961            } => {
9962                self.write_keyword("ADD");
9963                self.write_space();
9964                if *if_not_exists {
9965                    self.write_keyword("IF NOT EXISTS");
9966                    self.write_space();
9967                }
9968                self.generate_expression(partition)?;
9969                if let Some(ref loc) = location {
9970                    self.write_space();
9971                    self.write_keyword("LOCATION");
9972                    self.write_space();
9973                    self.generate_expression(loc)?;
9974                }
9975            }
9976            AlterTableAction::AlterSortKey {
9977                this,
9978                expressions,
9979                compound,
9980            } => {
9981                // Redshift: ALTER [COMPOUND] SORTKEY AUTO|NONE|(col1, col2)
9982                self.write_keyword("ALTER");
9983                if *compound {
9984                    self.write_space();
9985                    self.write_keyword("COMPOUND");
9986                }
9987                self.write_space();
9988                self.write_keyword("SORTKEY");
9989                self.write_space();
9990                if let Some(style) = this {
9991                    self.write_keyword(style);
9992                } else if !expressions.is_empty() {
9993                    self.write("(");
9994                    for (i, expr) in expressions.iter().enumerate() {
9995                        if i > 0 {
9996                            self.write(", ");
9997                        }
9998                        self.generate_expression(expr)?;
9999                    }
10000                    self.write(")");
10001                }
10002            }
10003            AlterTableAction::AlterDistStyle { style, distkey } => {
10004                // Redshift: ALTER DISTSTYLE ALL|EVEN|AUTO|KEY [DISTKEY col]
10005                self.write_keyword("ALTER");
10006                self.write_space();
10007                self.write_keyword("DISTSTYLE");
10008                self.write_space();
10009                self.write_keyword(style);
10010                if let Some(col) = distkey {
10011                    self.write_space();
10012                    self.write_keyword("DISTKEY");
10013                    self.write_space();
10014                    self.generate_identifier(col)?;
10015                }
10016            }
10017            AlterTableAction::SetTableProperties { properties } => {
10018                // Redshift: SET TABLE PROPERTIES ('a' = '5', 'b' = 'c')
10019                self.write_keyword("SET TABLE PROPERTIES");
10020                self.write(" (");
10021                for (i, (key, value)) in properties.iter().enumerate() {
10022                    if i > 0 {
10023                        self.write(", ");
10024                    }
10025                    self.generate_expression(key)?;
10026                    self.write(" = ");
10027                    self.generate_expression(value)?;
10028                }
10029                self.write(")");
10030            }
10031            AlterTableAction::SetLocation { location } => {
10032                // Redshift: SET LOCATION 's3://bucket/folder/'
10033                self.write_keyword("SET LOCATION");
10034                self.write_space();
10035                self.write("'");
10036                self.write(location);
10037                self.write("'");
10038            }
10039            AlterTableAction::SetFileFormat { format } => {
10040                // Redshift: SET FILE FORMAT AVRO
10041                self.write_keyword("SET FILE FORMAT");
10042                self.write_space();
10043                self.write_keyword(format);
10044            }
10045            AlterTableAction::ReplacePartition { partition, source } => {
10046                // ClickHouse: REPLACE PARTITION expr FROM source
10047                self.write_keyword("REPLACE PARTITION");
10048                self.write_space();
10049                self.generate_expression(partition)?;
10050                if let Some(src) = source {
10051                    self.write_space();
10052                    self.write_keyword("FROM");
10053                    self.write_space();
10054                    self.generate_expression(src)?;
10055                }
10056            }
10057            AlterTableAction::Raw { sql } => {
10058                self.write(sql);
10059            }
10060        }
10061        Ok(())
10062    }
10063
10064    fn generate_alter_column_action(&mut self, action: &AlterColumnAction) -> Result<()> {
10065        match action {
10066            AlterColumnAction::SetDataType {
10067                data_type,
10068                using,
10069                collate,
10070            } => {
10071                use crate::dialects::DialectType;
10072                // Dialect-specific type change syntax:
10073                // - TSQL/Fabric/Hive: no prefix (ALTER COLUMN col datatype)
10074                // - Redshift/Spark: TYPE (ALTER COLUMN col TYPE datatype)
10075                // - Default: SET DATA TYPE (ALTER COLUMN col SET DATA TYPE datatype)
10076                let is_no_prefix = matches!(
10077                    self.config.dialect,
10078                    Some(DialectType::TSQL) | Some(DialectType::Fabric) | Some(DialectType::Hive)
10079                );
10080                let is_type_only = matches!(
10081                    self.config.dialect,
10082                    Some(DialectType::Redshift)
10083                        | Some(DialectType::Spark)
10084                        | Some(DialectType::Databricks)
10085                );
10086                if is_type_only {
10087                    self.write_keyword("TYPE");
10088                    self.write_space();
10089                } else if !is_no_prefix {
10090                    self.write_keyword("SET DATA TYPE");
10091                    self.write_space();
10092                }
10093                self.generate_data_type(data_type)?;
10094                if let Some(ref collation) = collate {
10095                    self.write_space();
10096                    self.write_keyword("COLLATE");
10097                    self.write_space();
10098                    self.write(collation);
10099                }
10100                if let Some(ref using_expr) = using {
10101                    self.write_space();
10102                    self.write_keyword("USING");
10103                    self.write_space();
10104                    self.generate_expression(using_expr)?;
10105                }
10106            }
10107            AlterColumnAction::SetDefault(expr) => {
10108                self.write_keyword("SET DEFAULT");
10109                self.write_space();
10110                self.generate_expression(expr)?;
10111            }
10112            AlterColumnAction::DropDefault => {
10113                self.write_keyword("DROP DEFAULT");
10114            }
10115            AlterColumnAction::SetNotNull => {
10116                self.write_keyword("SET NOT NULL");
10117            }
10118            AlterColumnAction::DropNotNull => {
10119                self.write_keyword("DROP NOT NULL");
10120            }
10121            AlterColumnAction::Comment(comment) => {
10122                self.write_keyword("COMMENT");
10123                self.write_space();
10124                self.generate_string_literal(comment)?;
10125            }
10126            AlterColumnAction::SetVisible => {
10127                self.write_keyword("SET VISIBLE");
10128            }
10129            AlterColumnAction::SetInvisible => {
10130                self.write_keyword("SET INVISIBLE");
10131            }
10132        }
10133        Ok(())
10134    }
10135
10136    fn generate_create_index(&mut self, ci: &CreateIndex) -> Result<()> {
10137        self.write_keyword("CREATE");
10138
10139        if ci.unique {
10140            self.write_space();
10141            self.write_keyword("UNIQUE");
10142        }
10143
10144        // TSQL CLUSTERED/NONCLUSTERED modifier
10145        if let Some(ref clustered) = ci.clustered {
10146            self.write_space();
10147            self.write_keyword(clustered);
10148        }
10149
10150        self.write_space();
10151        self.write_keyword("INDEX");
10152
10153        // PostgreSQL CONCURRENTLY modifier
10154        if ci.concurrently {
10155            self.write_space();
10156            self.write_keyword("CONCURRENTLY");
10157        }
10158
10159        if ci.if_not_exists {
10160            self.write_space();
10161            self.write_keyword("IF NOT EXISTS");
10162        }
10163
10164        // Index name is optional in PostgreSQL when IF NOT EXISTS is specified
10165        if !ci.name.name.is_empty() {
10166            self.write_space();
10167            self.generate_identifier(&ci.name)?;
10168        }
10169        self.write_space();
10170        self.write_keyword("ON");
10171        // Hive uses ON TABLE
10172        if matches!(self.config.dialect, Some(DialectType::Hive)) {
10173            self.write_space();
10174            self.write_keyword("TABLE");
10175        }
10176        self.write_space();
10177        self.generate_table(&ci.table)?;
10178
10179        // Column list (optional for COLUMNSTORE indexes)
10180        // Standard SQL convention: ON t(a) without space before paren
10181        if !ci.columns.is_empty() || ci.using.is_some() {
10182            let space_before_paren = false;
10183
10184            if let Some(ref using) = ci.using {
10185                self.write_space();
10186                self.write_keyword("USING");
10187                self.write_space();
10188                self.write(using);
10189                if space_before_paren {
10190                    self.write(" (");
10191                } else {
10192                    self.write("(");
10193                }
10194            } else {
10195                if space_before_paren {
10196                    self.write(" (");
10197                } else {
10198                    self.write("(");
10199                }
10200            }
10201            for (i, col) in ci.columns.iter().enumerate() {
10202                if i > 0 {
10203                    self.write(", ");
10204                }
10205                self.generate_identifier(&col.column)?;
10206                if let Some(ref opclass) = col.opclass {
10207                    self.write_space();
10208                    self.write(opclass);
10209                }
10210                if col.desc {
10211                    self.write_space();
10212                    self.write_keyword("DESC");
10213                } else if col.asc {
10214                    self.write_space();
10215                    self.write_keyword("ASC");
10216                }
10217                if let Some(nulls_first) = col.nulls_first {
10218                    self.write_space();
10219                    self.write_keyword("NULLS");
10220                    self.write_space();
10221                    self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
10222                }
10223            }
10224            self.write(")");
10225        }
10226
10227        // PostgreSQL INCLUDE (col1, col2) clause
10228        if !ci.include_columns.is_empty() {
10229            self.write_space();
10230            self.write_keyword("INCLUDE");
10231            self.write(" (");
10232            for (i, col) in ci.include_columns.iter().enumerate() {
10233                if i > 0 {
10234                    self.write(", ");
10235                }
10236                self.generate_identifier(col)?;
10237            }
10238            self.write(")");
10239        }
10240
10241        // TSQL: WITH (option=value, ...) clause
10242        if !ci.with_options.is_empty() {
10243            self.write_space();
10244            self.write_keyword("WITH");
10245            self.write(" (");
10246            for (i, (key, value)) in ci.with_options.iter().enumerate() {
10247                if i > 0 {
10248                    self.write(", ");
10249                }
10250                self.write(key);
10251                self.write("=");
10252                self.write(value);
10253            }
10254            self.write(")");
10255        }
10256
10257        // PostgreSQL WHERE clause for partial indexes
10258        if let Some(ref where_clause) = ci.where_clause {
10259            self.write_space();
10260            self.write_keyword("WHERE");
10261            self.write_space();
10262            self.generate_expression(where_clause)?;
10263        }
10264
10265        // TSQL: ON filegroup or partition scheme clause
10266        if let Some(ref on_fg) = ci.on_filegroup {
10267            self.write_space();
10268            self.write_keyword("ON");
10269            self.write_space();
10270            self.write(on_fg);
10271        }
10272
10273        Ok(())
10274    }
10275
10276    fn generate_drop_index(&mut self, di: &DropIndex) -> Result<()> {
10277        self.write_keyword("DROP INDEX");
10278
10279        if di.concurrently {
10280            self.write_space();
10281            self.write_keyword("CONCURRENTLY");
10282        }
10283
10284        if di.if_exists {
10285            self.write_space();
10286            self.write_keyword("IF EXISTS");
10287        }
10288
10289        self.write_space();
10290        self.generate_identifier(&di.name)?;
10291
10292        if let Some(ref table) = di.table {
10293            self.write_space();
10294            self.write_keyword("ON");
10295            self.write_space();
10296            self.generate_table(table)?;
10297        }
10298
10299        Ok(())
10300    }
10301
10302    fn generate_create_view(&mut self, cv: &CreateView) -> Result<()> {
10303        self.write_keyword("CREATE");
10304
10305        // MySQL: ALGORITHM=...
10306        if let Some(ref algorithm) = cv.algorithm {
10307            self.write_space();
10308            self.write_keyword("ALGORITHM");
10309            self.write("=");
10310            self.write_keyword(algorithm);
10311        }
10312
10313        // MySQL: DEFINER=...
10314        if let Some(ref definer) = cv.definer {
10315            self.write_space();
10316            self.write_keyword("DEFINER");
10317            self.write("=");
10318            self.write(definer);
10319        }
10320
10321        // MySQL: SQL SECURITY DEFINER/INVOKER (before VIEW keyword)
10322        if cv.security_sql_style {
10323            if let Some(ref security) = cv.security {
10324                self.write_space();
10325                self.write_keyword("SQL SECURITY");
10326                self.write_space();
10327                match security {
10328                    FunctionSecurity::Definer => self.write_keyword("DEFINER"),
10329                    FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
10330                    FunctionSecurity::None => self.write_keyword("NONE"),
10331                }
10332            }
10333        }
10334
10335        if cv.or_replace {
10336            self.write_space();
10337            self.write_keyword("OR REPLACE");
10338        }
10339
10340        if cv.temporary {
10341            self.write_space();
10342            self.write_keyword("TEMPORARY");
10343        }
10344
10345        if cv.materialized {
10346            self.write_space();
10347            self.write_keyword("MATERIALIZED");
10348        }
10349
10350        // Snowflake: SECURE VIEW
10351        if cv.secure {
10352            self.write_space();
10353            self.write_keyword("SECURE");
10354        }
10355
10356        self.write_space();
10357        self.write_keyword("VIEW");
10358
10359        if cv.if_not_exists {
10360            self.write_space();
10361            self.write_keyword("IF NOT EXISTS");
10362        }
10363
10364        self.write_space();
10365        self.generate_table(&cv.name)?;
10366
10367        // ClickHouse: ON CLUSTER clause
10368        if let Some(ref on_cluster) = cv.on_cluster {
10369            self.write_space();
10370            self.generate_on_cluster(on_cluster)?;
10371        }
10372
10373        // ClickHouse: TO destination_table
10374        if let Some(ref to_table) = cv.to_table {
10375            self.write_space();
10376            self.write_keyword("TO");
10377            self.write_space();
10378            self.generate_table(to_table)?;
10379        }
10380
10381        // For regular VIEW: columns come before COPY GRANTS
10382        // For MATERIALIZED VIEW: COPY GRANTS comes before columns
10383        if !cv.materialized {
10384            // Regular VIEW: columns first
10385            if !cv.columns.is_empty() {
10386                self.write(" (");
10387                for (i, col) in cv.columns.iter().enumerate() {
10388                    if i > 0 {
10389                        self.write(", ");
10390                    }
10391                    self.generate_identifier(&col.name)?;
10392                    // BigQuery: OPTIONS (key=value, ...) on view column
10393                    if !col.options.is_empty() {
10394                        self.write_space();
10395                        self.generate_options_clause(&col.options)?;
10396                    }
10397                    if let Some(ref comment) = col.comment {
10398                        self.write_space();
10399                        self.write_keyword("COMMENT");
10400                        self.write_space();
10401                        self.generate_string_literal(comment)?;
10402                    }
10403                }
10404                self.write(")");
10405            }
10406
10407            // Presto/Trino/StarRocks: SECURITY DEFINER/INVOKER/NONE (after columns)
10408            if !cv.security_sql_style {
10409                if let Some(ref security) = cv.security {
10410                    self.write_space();
10411                    self.write_keyword("SECURITY");
10412                    self.write_space();
10413                    match security {
10414                        FunctionSecurity::Definer => self.write_keyword("DEFINER"),
10415                        FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
10416                        FunctionSecurity::None => self.write_keyword("NONE"),
10417                    }
10418                }
10419            }
10420
10421            // Snowflake: COPY GRANTS
10422            if cv.copy_grants {
10423                self.write_space();
10424                self.write_keyword("COPY GRANTS");
10425            }
10426        } else {
10427            // MATERIALIZED VIEW: COPY GRANTS first
10428            if cv.copy_grants {
10429                self.write_space();
10430                self.write_keyword("COPY GRANTS");
10431            }
10432
10433            // Doris: If we have a schema (typed columns), generate that instead
10434            if let Some(ref schema) = cv.schema {
10435                self.write(" (");
10436                for (i, expr) in schema.expressions.iter().enumerate() {
10437                    if i > 0 {
10438                        self.write(", ");
10439                    }
10440                    self.generate_expression(expr)?;
10441                }
10442                self.write(")");
10443            } else if !cv.columns.is_empty() {
10444                // Then columns (simple column names without types)
10445                self.write(" (");
10446                for (i, col) in cv.columns.iter().enumerate() {
10447                    if i > 0 {
10448                        self.write(", ");
10449                    }
10450                    self.generate_identifier(&col.name)?;
10451                    // BigQuery: OPTIONS (key=value, ...) on view column
10452                    if !col.options.is_empty() {
10453                        self.write_space();
10454                        self.generate_options_clause(&col.options)?;
10455                    }
10456                    if let Some(ref comment) = col.comment {
10457                        self.write_space();
10458                        self.write_keyword("COMMENT");
10459                        self.write_space();
10460                        self.generate_string_literal(comment)?;
10461                    }
10462                }
10463                self.write(")");
10464            }
10465
10466            // Doris: KEY (columns) for materialized views
10467            if let Some(ref unique_key) = cv.unique_key {
10468                self.write_space();
10469                self.write_keyword("KEY");
10470                self.write(" (");
10471                for (i, expr) in unique_key.expressions.iter().enumerate() {
10472                    if i > 0 {
10473                        self.write(", ");
10474                    }
10475                    self.generate_expression(expr)?;
10476                }
10477                self.write(")");
10478            }
10479        }
10480
10481        // Snowflake: COMMENT = 'text'
10482        if let Some(ref comment) = cv.comment {
10483            self.write_space();
10484            self.write_keyword("COMMENT");
10485            self.write("=");
10486            self.generate_string_literal(comment)?;
10487        }
10488
10489        // Snowflake: TAG (name='value', ...)
10490        if !cv.tags.is_empty() {
10491            self.write_space();
10492            self.write_keyword("TAG");
10493            self.write(" (");
10494            for (i, (name, value)) in cv.tags.iter().enumerate() {
10495                if i > 0 {
10496                    self.write(", ");
10497                }
10498                self.write(name);
10499                self.write("='");
10500                self.write(value);
10501                self.write("'");
10502            }
10503            self.write(")");
10504        }
10505
10506        // BigQuery: OPTIONS (key=value, ...)
10507        if !cv.options.is_empty() {
10508            self.write_space();
10509            self.generate_options_clause(&cv.options)?;
10510        }
10511
10512        // Doris: BUILD IMMEDIATE/DEFERRED for materialized views
10513        if let Some(ref build) = cv.build {
10514            self.write_space();
10515            self.write_keyword("BUILD");
10516            self.write_space();
10517            self.write_keyword(build);
10518        }
10519
10520        // Doris: REFRESH clause for materialized views
10521        if let Some(ref refresh) = cv.refresh {
10522            self.write_space();
10523            self.generate_refresh_trigger_property(refresh)?;
10524        }
10525
10526        // Redshift: AUTO REFRESH YES|NO for materialized views
10527        if let Some(auto_refresh) = cv.auto_refresh {
10528            self.write_space();
10529            self.write_keyword("AUTO REFRESH");
10530            self.write_space();
10531            if auto_refresh {
10532                self.write_keyword("YES");
10533            } else {
10534                self.write_keyword("NO");
10535            }
10536        }
10537
10538        // ClickHouse: Table properties (ENGINE, ORDER BY, SAMPLE, SETTINGS, TTL, etc.)
10539        for prop in &cv.table_properties {
10540            self.write_space();
10541            self.generate_expression(prop)?;
10542        }
10543
10544        // Only output AS clause if there's a real query (not just NULL placeholder)
10545        if !matches!(&cv.query, Expression::Null(_)) {
10546            self.write_space();
10547            self.write_keyword("AS");
10548            self.write_space();
10549
10550            // Teradata: LOCKING clause (between AS and query)
10551            if let Some(ref mode) = cv.locking_mode {
10552                self.write_keyword("LOCKING");
10553                self.write_space();
10554                self.write_keyword(mode);
10555                if let Some(ref access) = cv.locking_access {
10556                    self.write_space();
10557                    self.write_keyword("FOR");
10558                    self.write_space();
10559                    self.write_keyword(access);
10560                }
10561                self.write_space();
10562            }
10563
10564            if cv.query_parenthesized {
10565                self.write("(");
10566            }
10567            self.generate_expression(&cv.query)?;
10568            if cv.query_parenthesized {
10569                self.write(")");
10570            }
10571        }
10572
10573        // Redshift: WITH NO SCHEMA BINDING (after query)
10574        if cv.no_schema_binding {
10575            self.write_space();
10576            self.write_keyword("WITH NO SCHEMA BINDING");
10577        }
10578
10579        Ok(())
10580    }
10581
10582    fn generate_drop_view(&mut self, dv: &DropView) -> Result<()> {
10583        self.write_keyword("DROP");
10584
10585        if dv.materialized {
10586            self.write_space();
10587            self.write_keyword("MATERIALIZED");
10588        }
10589
10590        self.write_space();
10591        self.write_keyword("VIEW");
10592
10593        if dv.if_exists {
10594            self.write_space();
10595            self.write_keyword("IF EXISTS");
10596        }
10597
10598        self.write_space();
10599        self.generate_table(&dv.name)?;
10600
10601        Ok(())
10602    }
10603
10604    fn generate_truncate(&mut self, tr: &Truncate) -> Result<()> {
10605        match tr.target {
10606            TruncateTarget::Database => self.write_keyword("TRUNCATE DATABASE"),
10607            TruncateTarget::Table => self.write_keyword("TRUNCATE TABLE"),
10608        }
10609        if tr.if_exists {
10610            self.write_space();
10611            self.write_keyword("IF EXISTS");
10612        }
10613        self.write_space();
10614        self.generate_table(&tr.table)?;
10615
10616        // ClickHouse: ON CLUSTER clause
10617        if let Some(ref on_cluster) = tr.on_cluster {
10618            self.write_space();
10619            self.generate_on_cluster(on_cluster)?;
10620        }
10621
10622        // Check if first table has a * (multi-table with star)
10623        if !tr.extra_tables.is_empty() {
10624            // Check if the first entry matches the main table (star case)
10625            let skip_first = if let Some(first) = tr.extra_tables.first() {
10626                first.table.name == tr.table.name && first.star
10627            } else {
10628                false
10629            };
10630
10631            // PostgreSQL normalizes away the * suffix (it's the default behavior)
10632            let strip_star = matches!(
10633                self.config.dialect,
10634                Some(crate::dialects::DialectType::PostgreSQL)
10635                    | Some(crate::dialects::DialectType::Redshift)
10636            );
10637            if skip_first && !strip_star {
10638                self.write("*");
10639            }
10640
10641            // Generate additional tables
10642            for (i, entry) in tr.extra_tables.iter().enumerate() {
10643                if i == 0 && skip_first {
10644                    continue; // Already handled the star for first table
10645                }
10646                self.write(", ");
10647                self.generate_table(&entry.table)?;
10648                if entry.star && !strip_star {
10649                    self.write("*");
10650                }
10651            }
10652        }
10653
10654        // RESTART/CONTINUE IDENTITY
10655        if let Some(identity) = &tr.identity {
10656            self.write_space();
10657            match identity {
10658                TruncateIdentity::Restart => self.write_keyword("RESTART IDENTITY"),
10659                TruncateIdentity::Continue => self.write_keyword("CONTINUE IDENTITY"),
10660            }
10661        }
10662
10663        if tr.cascade {
10664            self.write_space();
10665            self.write_keyword("CASCADE");
10666        }
10667
10668        if tr.restrict {
10669            self.write_space();
10670            self.write_keyword("RESTRICT");
10671        }
10672
10673        // Output Hive PARTITION clause
10674        if let Some(ref partition) = tr.partition {
10675            self.write_space();
10676            self.generate_expression(partition)?;
10677        }
10678
10679        Ok(())
10680    }
10681
10682    fn generate_use(&mut self, u: &Use) -> Result<()> {
10683        // Teradata uses "DATABASE <name>" instead of "USE <name>"
10684        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
10685            self.write_keyword("DATABASE");
10686            self.write_space();
10687            self.generate_identifier(&u.this)?;
10688            return Ok(());
10689        }
10690
10691        self.write_keyword("USE");
10692
10693        if let Some(kind) = &u.kind {
10694            self.write_space();
10695            match kind {
10696                UseKind::Database => self.write_keyword("DATABASE"),
10697                UseKind::Schema => self.write_keyword("SCHEMA"),
10698                UseKind::Role => self.write_keyword("ROLE"),
10699                UseKind::Warehouse => self.write_keyword("WAREHOUSE"),
10700                UseKind::Catalog => self.write_keyword("CATALOG"),
10701                UseKind::SecondaryRoles => self.write_keyword("SECONDARY ROLES"),
10702            }
10703        }
10704
10705        self.write_space();
10706        // For SECONDARY ROLES, write the value as-is (ALL, NONE, or role names)
10707        // without quoting, since these are keywords not identifiers
10708        if matches!(&u.kind, Some(UseKind::SecondaryRoles)) {
10709            self.write(&u.this.name);
10710        } else {
10711            self.generate_identifier(&u.this)?;
10712        }
10713        Ok(())
10714    }
10715
10716    fn generate_cache(&mut self, c: &Cache) -> Result<()> {
10717        self.write_keyword("CACHE");
10718        if c.lazy {
10719            self.write_space();
10720            self.write_keyword("LAZY");
10721        }
10722        self.write_space();
10723        self.write_keyword("TABLE");
10724        self.write_space();
10725        self.generate_identifier(&c.table)?;
10726
10727        // OPTIONS clause
10728        if !c.options.is_empty() {
10729            self.write_space();
10730            self.write_keyword("OPTIONS");
10731            self.write("(");
10732            for (i, (key, value)) in c.options.iter().enumerate() {
10733                if i > 0 {
10734                    self.write(", ");
10735                }
10736                self.generate_expression(key)?;
10737                self.write(" = ");
10738                self.generate_expression(value)?;
10739            }
10740            self.write(")");
10741        }
10742
10743        // AS query
10744        if let Some(query) = &c.query {
10745            self.write_space();
10746            self.write_keyword("AS");
10747            self.write_space();
10748            self.generate_expression(query)?;
10749        }
10750
10751        Ok(())
10752    }
10753
10754    fn generate_uncache(&mut self, u: &Uncache) -> Result<()> {
10755        self.write_keyword("UNCACHE TABLE");
10756        if u.if_exists {
10757            self.write_space();
10758            self.write_keyword("IF EXISTS");
10759        }
10760        self.write_space();
10761        self.generate_identifier(&u.table)?;
10762        Ok(())
10763    }
10764
10765    fn generate_load_data(&mut self, l: &LoadData) -> Result<()> {
10766        self.write_keyword("LOAD DATA");
10767        if l.local {
10768            self.write_space();
10769            self.write_keyword("LOCAL");
10770        }
10771        self.write_space();
10772        self.write_keyword("INPATH");
10773        self.write_space();
10774        self.write("'");
10775        self.write(&l.inpath);
10776        self.write("'");
10777
10778        if l.overwrite {
10779            self.write_space();
10780            self.write_keyword("OVERWRITE");
10781        }
10782
10783        self.write_space();
10784        self.write_keyword("INTO TABLE");
10785        self.write_space();
10786        self.generate_expression(&l.table)?;
10787
10788        // PARTITION clause
10789        if !l.partition.is_empty() {
10790            self.write_space();
10791            self.write_keyword("PARTITION");
10792            self.write("(");
10793            for (i, (col, val)) in l.partition.iter().enumerate() {
10794                if i > 0 {
10795                    self.write(", ");
10796                }
10797                self.generate_identifier(col)?;
10798                self.write(" = ");
10799                self.generate_expression(val)?;
10800            }
10801            self.write(")");
10802        }
10803
10804        // INPUTFORMAT clause
10805        if let Some(fmt) = &l.input_format {
10806            self.write_space();
10807            self.write_keyword("INPUTFORMAT");
10808            self.write_space();
10809            self.write("'");
10810            self.write(fmt);
10811            self.write("'");
10812        }
10813
10814        // SERDE clause
10815        if let Some(serde) = &l.serde {
10816            self.write_space();
10817            self.write_keyword("SERDE");
10818            self.write_space();
10819            self.write("'");
10820            self.write(serde);
10821            self.write("'");
10822        }
10823
10824        Ok(())
10825    }
10826
10827    fn generate_pragma(&mut self, p: &Pragma) -> Result<()> {
10828        self.write_keyword("PRAGMA");
10829        self.write_space();
10830
10831        // Schema prefix if present
10832        if let Some(schema) = &p.schema {
10833            self.generate_identifier(schema)?;
10834            self.write(".");
10835        }
10836
10837        // Pragma name
10838        self.generate_identifier(&p.name)?;
10839
10840        // Value assignment or function call
10841        if let Some(value) = &p.value {
10842            self.write(" = ");
10843            self.generate_expression(value)?;
10844        } else if !p.args.is_empty() {
10845            self.write("(");
10846            for (i, arg) in p.args.iter().enumerate() {
10847                if i > 0 {
10848                    self.write(", ");
10849                }
10850                self.generate_expression(arg)?;
10851            }
10852            self.write(")");
10853        }
10854
10855        Ok(())
10856    }
10857
10858    fn generate_grant(&mut self, g: &Grant) -> Result<()> {
10859        self.write_keyword("GRANT");
10860        self.write_space();
10861
10862        // Privileges (with optional column lists)
10863        for (i, privilege) in g.privileges.iter().enumerate() {
10864            if i > 0 {
10865                self.write(", ");
10866            }
10867            self.write_keyword(&privilege.name);
10868            // Output column list if present: SELECT(col1, col2)
10869            if !privilege.columns.is_empty() {
10870                self.write("(");
10871                for (j, col) in privilege.columns.iter().enumerate() {
10872                    if j > 0 {
10873                        self.write(", ");
10874                    }
10875                    self.write(col);
10876                }
10877                self.write(")");
10878            }
10879        }
10880
10881        self.write_space();
10882        self.write_keyword("ON");
10883        self.write_space();
10884
10885        // Object kind (TABLE, SCHEMA, etc.)
10886        if let Some(kind) = &g.kind {
10887            self.write_keyword(kind);
10888            self.write_space();
10889        }
10890
10891        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
10892        {
10893            use crate::dialects::DialectType;
10894            let should_upper = matches!(
10895                self.config.dialect,
10896                Some(DialectType::PostgreSQL)
10897                    | Some(DialectType::CockroachDB)
10898                    | Some(DialectType::Materialize)
10899                    | Some(DialectType::RisingWave)
10900            ) && (g.kind.as_deref() == Some("FUNCTION")
10901                || g.kind.as_deref() == Some("PROCEDURE"));
10902            if should_upper {
10903                use crate::expressions::Identifier;
10904                let upper_id = Identifier {
10905                    name: g.securable.name.to_uppercase(),
10906                    quoted: g.securable.quoted,
10907                    ..g.securable.clone()
10908                };
10909                self.generate_identifier(&upper_id)?;
10910            } else {
10911                self.generate_identifier(&g.securable)?;
10912            }
10913        }
10914
10915        // Function parameter types (if present)
10916        if !g.function_params.is_empty() {
10917            self.write("(");
10918            for (i, param) in g.function_params.iter().enumerate() {
10919                if i > 0 {
10920                    self.write(", ");
10921                }
10922                self.write(param);
10923            }
10924            self.write(")");
10925        }
10926
10927        self.write_space();
10928        self.write_keyword("TO");
10929        self.write_space();
10930
10931        // Principals
10932        for (i, principal) in g.principals.iter().enumerate() {
10933            if i > 0 {
10934                self.write(", ");
10935            }
10936            if principal.is_role {
10937                self.write_keyword("ROLE");
10938                self.write_space();
10939            } else if principal.is_group {
10940                self.write_keyword("GROUP");
10941                self.write_space();
10942            }
10943            self.generate_identifier(&principal.name)?;
10944        }
10945
10946        // WITH GRANT OPTION
10947        if g.grant_option {
10948            self.write_space();
10949            self.write_keyword("WITH GRANT OPTION");
10950        }
10951
10952        // TSQL: AS principal
10953        if let Some(ref principal) = g.as_principal {
10954            self.write_space();
10955            self.write_keyword("AS");
10956            self.write_space();
10957            self.generate_identifier(principal)?;
10958        }
10959
10960        Ok(())
10961    }
10962
10963    fn generate_revoke(&mut self, r: &Revoke) -> Result<()> {
10964        self.write_keyword("REVOKE");
10965        self.write_space();
10966
10967        // GRANT OPTION FOR
10968        if r.grant_option {
10969            self.write_keyword("GRANT OPTION FOR");
10970            self.write_space();
10971        }
10972
10973        // Privileges (with optional column lists)
10974        for (i, privilege) in r.privileges.iter().enumerate() {
10975            if i > 0 {
10976                self.write(", ");
10977            }
10978            self.write_keyword(&privilege.name);
10979            // Output column list if present: SELECT(col1, col2)
10980            if !privilege.columns.is_empty() {
10981                self.write("(");
10982                for (j, col) in privilege.columns.iter().enumerate() {
10983                    if j > 0 {
10984                        self.write(", ");
10985                    }
10986                    self.write(col);
10987                }
10988                self.write(")");
10989            }
10990        }
10991
10992        self.write_space();
10993        self.write_keyword("ON");
10994        self.write_space();
10995
10996        // Object kind
10997        if let Some(kind) = &r.kind {
10998            self.write_keyword(kind);
10999            self.write_space();
11000        }
11001
11002        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
11003        {
11004            use crate::dialects::DialectType;
11005            let should_upper = matches!(
11006                self.config.dialect,
11007                Some(DialectType::PostgreSQL)
11008                    | Some(DialectType::CockroachDB)
11009                    | Some(DialectType::Materialize)
11010                    | Some(DialectType::RisingWave)
11011            ) && (r.kind.as_deref() == Some("FUNCTION")
11012                || r.kind.as_deref() == Some("PROCEDURE"));
11013            if should_upper {
11014                use crate::expressions::Identifier;
11015                let upper_id = Identifier {
11016                    name: r.securable.name.to_uppercase(),
11017                    quoted: r.securable.quoted,
11018                    ..r.securable.clone()
11019                };
11020                self.generate_identifier(&upper_id)?;
11021            } else {
11022                self.generate_identifier(&r.securable)?;
11023            }
11024        }
11025
11026        // Function parameter types (if present)
11027        if !r.function_params.is_empty() {
11028            self.write("(");
11029            for (i, param) in r.function_params.iter().enumerate() {
11030                if i > 0 {
11031                    self.write(", ");
11032                }
11033                self.write(param);
11034            }
11035            self.write(")");
11036        }
11037
11038        self.write_space();
11039        self.write_keyword("FROM");
11040        self.write_space();
11041
11042        // Principals
11043        for (i, principal) in r.principals.iter().enumerate() {
11044            if i > 0 {
11045                self.write(", ");
11046            }
11047            if principal.is_role {
11048                self.write_keyword("ROLE");
11049                self.write_space();
11050            } else if principal.is_group {
11051                self.write_keyword("GROUP");
11052                self.write_space();
11053            }
11054            self.generate_identifier(&principal.name)?;
11055        }
11056
11057        // CASCADE or RESTRICT
11058        if r.cascade {
11059            self.write_space();
11060            self.write_keyword("CASCADE");
11061        } else if r.restrict {
11062            self.write_space();
11063            self.write_keyword("RESTRICT");
11064        }
11065
11066        Ok(())
11067    }
11068
11069    fn generate_comment(&mut self, c: &Comment) -> Result<()> {
11070        self.write_keyword("COMMENT");
11071
11072        // IF EXISTS
11073        if c.exists {
11074            self.write_space();
11075            self.write_keyword("IF EXISTS");
11076        }
11077
11078        self.write_space();
11079        self.write_keyword("ON");
11080
11081        // MATERIALIZED
11082        if c.materialized {
11083            self.write_space();
11084            self.write_keyword("MATERIALIZED");
11085        }
11086
11087        self.write_space();
11088        self.write_keyword(&c.kind);
11089        self.write_space();
11090
11091        // Object name
11092        self.generate_expression(&c.this)?;
11093
11094        self.write_space();
11095        self.write_keyword("IS");
11096        self.write_space();
11097
11098        // Comment expression
11099        self.generate_expression(&c.expression)?;
11100
11101        Ok(())
11102    }
11103
11104    fn generate_set_statement(&mut self, s: &SetStatement) -> Result<()> {
11105        self.write_keyword("SET");
11106
11107        for (i, item) in s.items.iter().enumerate() {
11108            if i > 0 {
11109                self.write(",");
11110            }
11111            self.write_space();
11112
11113            // Kind modifier (GLOBAL, LOCAL, SESSION, PERSIST, PERSIST_ONLY)
11114            if let Some(ref kind) = item.kind {
11115                self.write_keyword(kind);
11116                self.write_space();
11117            }
11118
11119            // Check for special SET forms by name
11120            let name_str = match &item.name {
11121                Expression::Identifier(id) => Some(id.name.as_str()),
11122                _ => None,
11123            };
11124
11125            let is_transaction = name_str == Some("TRANSACTION");
11126            let is_character_set = name_str == Some("CHARACTER SET");
11127            let is_names = name_str == Some("NAMES");
11128            let is_collate = name_str == Some("COLLATE");
11129            let has_variable_kind = item.kind.as_deref() == Some("VARIABLE");
11130            let name_has_variable_prefix = name_str.map_or(false, |n| n.starts_with("VARIABLE "));
11131            let is_variable = has_variable_kind || name_has_variable_prefix;
11132            let is_value_only =
11133                matches!(&item.value, Expression::Identifier(id) if id.name.is_empty());
11134
11135            if is_transaction {
11136                // Output: SET [GLOBAL|SESSION] TRANSACTION <characteristics>
11137                self.write_keyword("TRANSACTION");
11138                if let Expression::Identifier(id) = &item.value {
11139                    if !id.name.is_empty() {
11140                        self.write_space();
11141                        self.write(&id.name);
11142                    }
11143                }
11144            } else if is_character_set {
11145                // Output: SET CHARACTER SET <charset>
11146                self.write_keyword("CHARACTER SET");
11147                self.write_space();
11148                self.generate_set_value(&item.value)?;
11149            } else if is_names {
11150                // Output: SET NAMES <charset>
11151                self.write_keyword("NAMES");
11152                self.write_space();
11153                self.generate_set_value(&item.value)?;
11154            } else if is_collate {
11155                // Output: COLLATE <collation> (part of SET NAMES ... COLLATE ...)
11156                self.write_keyword("COLLATE");
11157                self.write_space();
11158                self.generate_set_value(&item.value)?;
11159            } else if is_variable {
11160                // Output: SET [VARIABLE] <name> = <value>
11161                // If kind=VARIABLE, the keyword was already written above.
11162                // If name has VARIABLE prefix, write VARIABLE keyword for DuckDB target only.
11163                if name_has_variable_prefix && !has_variable_kind {
11164                    if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
11165                        self.write_keyword("VARIABLE");
11166                        self.write_space();
11167                    }
11168                }
11169                // Extract actual variable name (strip VARIABLE prefix if present)
11170                if let Some(ns) = name_str {
11171                    let var_name = if name_has_variable_prefix {
11172                        &ns["VARIABLE ".len()..]
11173                    } else {
11174                        ns
11175                    };
11176                    self.write(var_name);
11177                } else {
11178                    self.generate_expression(&item.name)?;
11179                }
11180                self.write(" = ");
11181                self.generate_set_value(&item.value)?;
11182            } else if is_value_only {
11183                // SET <name> ON/OFF without = (TSQL: SET XACT_ABORT ON)
11184                self.generate_expression(&item.name)?;
11185            } else if item.no_equals && matches!(self.config.dialect, Some(DialectType::TSQL)) {
11186                // SET key value without = (TSQL style)
11187                self.generate_expression(&item.name)?;
11188                self.write_space();
11189                self.generate_set_value(&item.value)?;
11190            } else {
11191                // Standard: variable = value
11192                // SET item names should not be quoted (they are config parameter names, not column refs)
11193                match &item.name {
11194                    Expression::Identifier(id) => {
11195                        self.write(&id.name);
11196                    }
11197                    _ => {
11198                        self.generate_expression(&item.name)?;
11199                    }
11200                }
11201                self.write(" = ");
11202                self.generate_set_value(&item.value)?;
11203            }
11204        }
11205
11206        Ok(())
11207    }
11208
11209    /// Generate a SET statement value, writing keyword values (DEFAULT, ON, OFF)
11210    /// directly to avoid reserved keyword quoting.
11211    fn generate_set_value(&mut self, value: &Expression) -> Result<()> {
11212        if let Expression::Identifier(id) = value {
11213            match id.name.as_str() {
11214                "DEFAULT" | "ON" | "OFF" => {
11215                    self.write_keyword(&id.name);
11216                    return Ok(());
11217                }
11218                _ => {}
11219            }
11220        }
11221        self.generate_expression(value)
11222    }
11223
11224    // ==================== Phase 4: Additional DDL Generation ====================
11225
11226    fn generate_alter_view(&mut self, av: &AlterView) -> Result<()> {
11227        self.write_keyword("ALTER");
11228        // MySQL modifiers before VIEW
11229        if let Some(ref algorithm) = av.algorithm {
11230            self.write_space();
11231            self.write_keyword("ALGORITHM");
11232            self.write(" = ");
11233            self.write_keyword(algorithm);
11234        }
11235        if let Some(ref definer) = av.definer {
11236            self.write_space();
11237            self.write_keyword("DEFINER");
11238            self.write(" = ");
11239            self.write(definer);
11240        }
11241        if let Some(ref sql_security) = av.sql_security {
11242            self.write_space();
11243            self.write_keyword("SQL SECURITY");
11244            self.write(" = ");
11245            self.write_keyword(sql_security);
11246        }
11247        self.write_space();
11248        self.write_keyword("VIEW");
11249        self.write_space();
11250        self.generate_table(&av.name)?;
11251
11252        // Hive: Column aliases with optional COMMENT
11253        if !av.columns.is_empty() {
11254            self.write(" (");
11255            for (i, col) in av.columns.iter().enumerate() {
11256                if i > 0 {
11257                    self.write(", ");
11258                }
11259                self.generate_identifier(&col.name)?;
11260                if let Some(ref comment) = col.comment {
11261                    self.write_space();
11262                    self.write_keyword("COMMENT");
11263                    self.write(" ");
11264                    self.generate_string_literal(comment)?;
11265                }
11266            }
11267            self.write(")");
11268        }
11269
11270        // TSQL: WITH option before actions
11271        if let Some(ref opt) = av.with_option {
11272            self.write_space();
11273            self.write_keyword("WITH");
11274            self.write_space();
11275            self.write_keyword(opt);
11276        }
11277
11278        for action in &av.actions {
11279            self.write_space();
11280            match action {
11281                AlterViewAction::Rename(new_name) => {
11282                    self.write_keyword("RENAME TO");
11283                    self.write_space();
11284                    self.generate_table(new_name)?;
11285                }
11286                AlterViewAction::OwnerTo(owner) => {
11287                    self.write_keyword("OWNER TO");
11288                    self.write_space();
11289                    self.generate_identifier(owner)?;
11290                }
11291                AlterViewAction::SetSchema(schema) => {
11292                    self.write_keyword("SET SCHEMA");
11293                    self.write_space();
11294                    self.generate_identifier(schema)?;
11295                }
11296                AlterViewAction::SetAuthorization(auth) => {
11297                    self.write_keyword("SET AUTHORIZATION");
11298                    self.write_space();
11299                    self.write(auth);
11300                }
11301                AlterViewAction::AlterColumn { name, action } => {
11302                    self.write_keyword("ALTER COLUMN");
11303                    self.write_space();
11304                    self.generate_identifier(name)?;
11305                    self.write_space();
11306                    self.generate_alter_column_action(action)?;
11307                }
11308                AlterViewAction::AsSelect(query) => {
11309                    self.write_keyword("AS");
11310                    self.write_space();
11311                    self.generate_expression(query)?;
11312                }
11313                AlterViewAction::SetTblproperties(props) => {
11314                    self.write_keyword("SET TBLPROPERTIES");
11315                    self.write(" (");
11316                    for (i, (key, value)) in props.iter().enumerate() {
11317                        if i > 0 {
11318                            self.write(", ");
11319                        }
11320                        self.generate_string_literal(key)?;
11321                        self.write("=");
11322                        self.generate_string_literal(value)?;
11323                    }
11324                    self.write(")");
11325                }
11326                AlterViewAction::UnsetTblproperties(keys) => {
11327                    self.write_keyword("UNSET TBLPROPERTIES");
11328                    self.write(" (");
11329                    for (i, key) in keys.iter().enumerate() {
11330                        if i > 0 {
11331                            self.write(", ");
11332                        }
11333                        self.generate_string_literal(key)?;
11334                    }
11335                    self.write(")");
11336                }
11337            }
11338        }
11339
11340        Ok(())
11341    }
11342
11343    fn generate_alter_index(&mut self, ai: &AlterIndex) -> Result<()> {
11344        self.write_keyword("ALTER INDEX");
11345        self.write_space();
11346        self.generate_identifier(&ai.name)?;
11347
11348        if let Some(table) = &ai.table {
11349            self.write_space();
11350            self.write_keyword("ON");
11351            self.write_space();
11352            self.generate_table(table)?;
11353        }
11354
11355        for action in &ai.actions {
11356            self.write_space();
11357            match action {
11358                AlterIndexAction::Rename(new_name) => {
11359                    self.write_keyword("RENAME TO");
11360                    self.write_space();
11361                    self.generate_identifier(new_name)?;
11362                }
11363                AlterIndexAction::SetTablespace(tablespace) => {
11364                    self.write_keyword("SET TABLESPACE");
11365                    self.write_space();
11366                    self.generate_identifier(tablespace)?;
11367                }
11368                AlterIndexAction::Visible(visible) => {
11369                    if *visible {
11370                        self.write_keyword("VISIBLE");
11371                    } else {
11372                        self.write_keyword("INVISIBLE");
11373                    }
11374                }
11375            }
11376        }
11377
11378        Ok(())
11379    }
11380
11381    fn generate_create_schema(&mut self, cs: &CreateSchema) -> Result<()> {
11382        // Output leading comments
11383        for comment in &cs.leading_comments {
11384            self.write_formatted_comment(comment);
11385            self.write_space();
11386        }
11387
11388        // Athena: CREATE SCHEMA uses Hive engine (backticks)
11389        let saved_athena_hive_context = self.athena_hive_context;
11390        if matches!(
11391            self.config.dialect,
11392            Some(crate::dialects::DialectType::Athena)
11393        ) {
11394            self.athena_hive_context = true;
11395        }
11396
11397        self.write_keyword("CREATE SCHEMA");
11398
11399        if cs.if_not_exists {
11400            self.write_space();
11401            self.write_keyword("IF NOT EXISTS");
11402        }
11403
11404        self.write_space();
11405        self.generate_identifier(&cs.name)?;
11406
11407        if let Some(ref clone_src) = cs.clone_from {
11408            self.write_keyword(" CLONE ");
11409            self.generate_identifier(clone_src)?;
11410        }
11411
11412        if let Some(ref at_clause) = cs.at_clause {
11413            self.write_space();
11414            self.generate_expression(at_clause)?;
11415        }
11416
11417        if let Some(auth) = &cs.authorization {
11418            self.write_space();
11419            self.write_keyword("AUTHORIZATION");
11420            self.write_space();
11421            self.generate_identifier(auth)?;
11422        }
11423
11424        // Generate schema properties (e.g., DEFAULT COLLATE or WITH (props))
11425        // Separate WITH properties from other properties
11426        let with_properties: Vec<_> = cs
11427            .properties
11428            .iter()
11429            .filter(|p| matches!(p, Expression::Property(_)))
11430            .collect();
11431        let other_properties: Vec<_> = cs
11432            .properties
11433            .iter()
11434            .filter(|p| !matches!(p, Expression::Property(_)))
11435            .collect();
11436
11437        // Generate WITH (props) if we have Property expressions
11438        if !with_properties.is_empty() {
11439            self.write_space();
11440            self.write_keyword("WITH");
11441            self.write(" (");
11442            for (i, prop) in with_properties.iter().enumerate() {
11443                if i > 0 {
11444                    self.write(", ");
11445                }
11446                self.generate_expression(prop)?;
11447            }
11448            self.write(")");
11449        }
11450
11451        // Generate other properties (like DEFAULT COLLATE)
11452        for prop in other_properties {
11453            self.write_space();
11454            self.generate_expression(prop)?;
11455        }
11456
11457        // Restore Athena Hive context
11458        self.athena_hive_context = saved_athena_hive_context;
11459
11460        Ok(())
11461    }
11462
11463    fn generate_drop_schema(&mut self, ds: &DropSchema) -> Result<()> {
11464        self.write_keyword("DROP SCHEMA");
11465
11466        if ds.if_exists {
11467            self.write_space();
11468            self.write_keyword("IF EXISTS");
11469        }
11470
11471        self.write_space();
11472        self.generate_identifier(&ds.name)?;
11473
11474        if ds.cascade {
11475            self.write_space();
11476            self.write_keyword("CASCADE");
11477        }
11478
11479        Ok(())
11480    }
11481
11482    fn generate_drop_namespace(&mut self, dn: &DropNamespace) -> Result<()> {
11483        self.write_keyword("DROP NAMESPACE");
11484
11485        if dn.if_exists {
11486            self.write_space();
11487            self.write_keyword("IF EXISTS");
11488        }
11489
11490        self.write_space();
11491        self.generate_identifier(&dn.name)?;
11492
11493        if dn.cascade {
11494            self.write_space();
11495            self.write_keyword("CASCADE");
11496        }
11497
11498        Ok(())
11499    }
11500
11501    fn generate_create_database(&mut self, cd: &CreateDatabase) -> Result<()> {
11502        self.write_keyword("CREATE DATABASE");
11503
11504        if cd.if_not_exists {
11505            self.write_space();
11506            self.write_keyword("IF NOT EXISTS");
11507        }
11508
11509        self.write_space();
11510        self.generate_identifier(&cd.name)?;
11511
11512        if let Some(ref clone_src) = cd.clone_from {
11513            self.write_keyword(" CLONE ");
11514            self.generate_identifier(clone_src)?;
11515        }
11516
11517        // AT/BEFORE clause for time travel (Snowflake)
11518        if let Some(ref at_clause) = cd.at_clause {
11519            self.write_space();
11520            self.generate_expression(at_clause)?;
11521        }
11522
11523        for option in &cd.options {
11524            self.write_space();
11525            match option {
11526                DatabaseOption::CharacterSet(charset) => {
11527                    self.write_keyword("CHARACTER SET");
11528                    self.write(" = ");
11529                    self.write(&format!("'{}'", charset));
11530                }
11531                DatabaseOption::Collate(collate) => {
11532                    self.write_keyword("COLLATE");
11533                    self.write(" = ");
11534                    self.write(&format!("'{}'", collate));
11535                }
11536                DatabaseOption::Owner(owner) => {
11537                    self.write_keyword("OWNER");
11538                    self.write(" = ");
11539                    self.generate_identifier(owner)?;
11540                }
11541                DatabaseOption::Template(template) => {
11542                    self.write_keyword("TEMPLATE");
11543                    self.write(" = ");
11544                    self.generate_identifier(template)?;
11545                }
11546                DatabaseOption::Encoding(encoding) => {
11547                    self.write_keyword("ENCODING");
11548                    self.write(" = ");
11549                    self.write(&format!("'{}'", encoding));
11550                }
11551                DatabaseOption::Location(location) => {
11552                    self.write_keyword("LOCATION");
11553                    self.write(" = ");
11554                    self.write(&format!("'{}'", location));
11555                }
11556            }
11557        }
11558
11559        Ok(())
11560    }
11561
11562    fn generate_drop_database(&mut self, dd: &DropDatabase) -> Result<()> {
11563        self.write_keyword("DROP DATABASE");
11564
11565        if dd.if_exists {
11566            self.write_space();
11567            self.write_keyword("IF EXISTS");
11568        }
11569
11570        self.write_space();
11571        self.generate_identifier(&dd.name)?;
11572
11573        Ok(())
11574    }
11575
11576    fn generate_create_function(&mut self, cf: &CreateFunction) -> Result<()> {
11577        self.write_keyword("CREATE");
11578
11579        if cf.or_replace {
11580            self.write_space();
11581            self.write_keyword("OR REPLACE");
11582        }
11583
11584        if cf.temporary {
11585            self.write_space();
11586            self.write_keyword("TEMPORARY");
11587        }
11588
11589        self.write_space();
11590        if cf.is_table_function {
11591            self.write_keyword("TABLE FUNCTION");
11592        } else {
11593            self.write_keyword("FUNCTION");
11594        }
11595
11596        if cf.if_not_exists {
11597            self.write_space();
11598            self.write_keyword("IF NOT EXISTS");
11599        }
11600
11601        self.write_space();
11602        self.generate_table(&cf.name)?;
11603        if cf.has_parens {
11604            let func_multiline = self.config.pretty
11605                && matches!(
11606                    self.config.dialect,
11607                    Some(crate::dialects::DialectType::TSQL)
11608                        | Some(crate::dialects::DialectType::Fabric)
11609                )
11610                && !cf.parameters.is_empty();
11611            if func_multiline {
11612                self.write("(\n");
11613                self.indent_level += 2;
11614                self.write_indent();
11615                self.generate_function_parameters(&cf.parameters)?;
11616                self.write("\n");
11617                self.indent_level -= 2;
11618                self.write(")");
11619            } else {
11620                self.write("(");
11621                self.generate_function_parameters(&cf.parameters)?;
11622                self.write(")");
11623            }
11624        }
11625
11626        // Output RETURNS clause (always comes first after parameters)
11627        // BigQuery and TSQL use multiline formatting for CREATE FUNCTION structure
11628        let use_multiline = self.config.pretty
11629            && matches!(
11630                self.config.dialect,
11631                Some(crate::dialects::DialectType::BigQuery)
11632                    | Some(crate::dialects::DialectType::TSQL)
11633                    | Some(crate::dialects::DialectType::Fabric)
11634            );
11635
11636        if cf.language_first {
11637            // LANGUAGE first, then SQL data access, then RETURNS
11638            if let Some(lang) = &cf.language {
11639                if use_multiline {
11640                    self.write_newline();
11641                } else {
11642                    self.write_space();
11643                }
11644                self.write_keyword("LANGUAGE");
11645                self.write_space();
11646                self.write(lang);
11647            }
11648
11649            // SQL data access comes after LANGUAGE in this case
11650            if let Some(sql_data) = &cf.sql_data_access {
11651                self.write_space();
11652                match sql_data {
11653                    SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
11654                    SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
11655                    SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
11656                    SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
11657                }
11658            }
11659
11660            if let Some(ref rtb) = cf.returns_table_body {
11661                if use_multiline {
11662                    self.write_newline();
11663                } else {
11664                    self.write_space();
11665                }
11666                self.write_keyword("RETURNS");
11667                self.write_space();
11668                self.write(rtb);
11669            } else if let Some(return_type) = &cf.return_type {
11670                if use_multiline {
11671                    self.write_newline();
11672                } else {
11673                    self.write_space();
11674                }
11675                self.write_keyword("RETURNS");
11676                self.write_space();
11677                self.generate_data_type(return_type)?;
11678            }
11679        } else {
11680            // RETURNS first (default)
11681            // DuckDB macros: skip RETURNS output (empty marker in returns_table_body means TABLE return)
11682            let is_duckdb = matches!(
11683                self.config.dialect,
11684                Some(crate::dialects::DialectType::DuckDB)
11685            );
11686            if let Some(ref rtb) = cf.returns_table_body {
11687                if !(is_duckdb && rtb.is_empty()) {
11688                    if use_multiline {
11689                        self.write_newline();
11690                    } else {
11691                        self.write_space();
11692                    }
11693                    self.write_keyword("RETURNS");
11694                    self.write_space();
11695                    self.write(rtb);
11696                }
11697            } else if let Some(return_type) = &cf.return_type {
11698                if use_multiline {
11699                    self.write_newline();
11700                } else {
11701                    self.write_space();
11702                }
11703                self.write_keyword("RETURNS");
11704                self.write_space();
11705                self.generate_data_type(return_type)?;
11706            }
11707        }
11708
11709        // If we have property_order, use it to output properties in original order
11710        if !cf.property_order.is_empty() {
11711            // For BigQuery, OPTIONS must come before AS - reorder if needed
11712            let is_bigquery = matches!(
11713                self.config.dialect,
11714                Some(crate::dialects::DialectType::BigQuery)
11715            );
11716            let property_order = if is_bigquery {
11717                // Move Options before As if both are present
11718                let mut reordered = Vec::new();
11719                let mut has_as = false;
11720                let mut has_options = false;
11721                for prop in &cf.property_order {
11722                    match prop {
11723                        FunctionPropertyKind::As => has_as = true,
11724                        FunctionPropertyKind::Options => has_options = true,
11725                        _ => {}
11726                    }
11727                }
11728                if has_as && has_options {
11729                    // Output all props except As and Options, then Options, then As
11730                    for prop in &cf.property_order {
11731                        if *prop != FunctionPropertyKind::As
11732                            && *prop != FunctionPropertyKind::Options
11733                        {
11734                            reordered.push(*prop);
11735                        }
11736                    }
11737                    reordered.push(FunctionPropertyKind::Options);
11738                    reordered.push(FunctionPropertyKind::As);
11739                    reordered
11740                } else {
11741                    cf.property_order.clone()
11742                }
11743            } else {
11744                cf.property_order.clone()
11745            };
11746
11747            for prop in &property_order {
11748                match prop {
11749                    FunctionPropertyKind::Set => {
11750                        self.generate_function_set_options(cf)?;
11751                    }
11752                    FunctionPropertyKind::As => {
11753                        self.generate_function_body(cf)?;
11754                    }
11755                    FunctionPropertyKind::Language => {
11756                        if !cf.language_first {
11757                            // Only output here if not already output above
11758                            if let Some(lang) = &cf.language {
11759                                // Only BigQuery uses multiline formatting
11760                                let use_multiline = self.config.pretty
11761                                    && matches!(
11762                                        self.config.dialect,
11763                                        Some(crate::dialects::DialectType::BigQuery)
11764                                    );
11765                                if use_multiline {
11766                                    self.write_newline();
11767                                } else {
11768                                    self.write_space();
11769                                }
11770                                self.write_keyword("LANGUAGE");
11771                                self.write_space();
11772                                self.write(lang);
11773                            }
11774                        }
11775                    }
11776                    FunctionPropertyKind::Determinism => {
11777                        self.generate_function_determinism(cf)?;
11778                    }
11779                    FunctionPropertyKind::NullInput => {
11780                        self.generate_function_null_input(cf)?;
11781                    }
11782                    FunctionPropertyKind::Security => {
11783                        self.generate_function_security(cf)?;
11784                    }
11785                    FunctionPropertyKind::SqlDataAccess => {
11786                        if !cf.language_first {
11787                            // Only output here if not already output above
11788                            self.generate_function_sql_data_access(cf)?;
11789                        }
11790                    }
11791                    FunctionPropertyKind::Options => {
11792                        if !cf.options.is_empty() {
11793                            self.write_space();
11794                            self.generate_options_clause(&cf.options)?;
11795                        }
11796                    }
11797                    FunctionPropertyKind::Environment => {
11798                        if !cf.environment.is_empty() {
11799                            self.write_space();
11800                            self.generate_environment_clause(&cf.environment)?;
11801                        }
11802                    }
11803                }
11804            }
11805
11806            // Output OPTIONS if not tracked in property_order (legacy)
11807            if !cf.options.is_empty() && !cf.property_order.contains(&FunctionPropertyKind::Options)
11808            {
11809                self.write_space();
11810                self.generate_options_clause(&cf.options)?;
11811            }
11812
11813            // Output ENVIRONMENT if not tracked in property_order (legacy)
11814            if !cf.environment.is_empty()
11815                && !cf
11816                    .property_order
11817                    .contains(&FunctionPropertyKind::Environment)
11818            {
11819                self.write_space();
11820                self.generate_environment_clause(&cf.environment)?;
11821            }
11822        } else {
11823            // Legacy behavior when property_order is empty
11824            // BigQuery: DETERMINISTIC/NOT DETERMINISTIC comes before LANGUAGE
11825            if matches!(
11826                self.config.dialect,
11827                Some(crate::dialects::DialectType::BigQuery)
11828            ) {
11829                self.generate_function_determinism(cf)?;
11830            }
11831
11832            // Only BigQuery uses multiline formatting for CREATE FUNCTION structure
11833            let use_multiline = self.config.pretty
11834                && matches!(
11835                    self.config.dialect,
11836                    Some(crate::dialects::DialectType::BigQuery)
11837                );
11838
11839            if !cf.language_first {
11840                if let Some(lang) = &cf.language {
11841                    if use_multiline {
11842                        self.write_newline();
11843                    } else {
11844                        self.write_space();
11845                    }
11846                    self.write_keyword("LANGUAGE");
11847                    self.write_space();
11848                    self.write(lang);
11849                }
11850
11851                // SQL data access characteristic comes after LANGUAGE
11852                self.generate_function_sql_data_access(cf)?;
11853            }
11854
11855            // For non-BigQuery dialects, output DETERMINISTIC/IMMUTABLE/VOLATILE here
11856            if !matches!(
11857                self.config.dialect,
11858                Some(crate::dialects::DialectType::BigQuery)
11859            ) {
11860                self.generate_function_determinism(cf)?;
11861            }
11862
11863            self.generate_function_null_input(cf)?;
11864            self.generate_function_security(cf)?;
11865            self.generate_function_set_options(cf)?;
11866
11867            // BigQuery: OPTIONS (key=value, ...) - comes before AS
11868            if !cf.options.is_empty() {
11869                self.write_space();
11870                self.generate_options_clause(&cf.options)?;
11871            }
11872
11873            // Databricks: ENVIRONMENT (dependencies = '...', ...) - comes before AS
11874            if !cf.environment.is_empty() {
11875                self.write_space();
11876                self.generate_environment_clause(&cf.environment)?;
11877            }
11878
11879            self.generate_function_body(cf)?;
11880        }
11881
11882        Ok(())
11883    }
11884
11885    /// Generate SET options for CREATE FUNCTION
11886    fn generate_function_set_options(&mut self, cf: &CreateFunction) -> Result<()> {
11887        for opt in &cf.set_options {
11888            self.write_space();
11889            self.write_keyword("SET");
11890            self.write_space();
11891            self.write(&opt.name);
11892            match &opt.value {
11893                FunctionSetValue::Value { value, use_to } => {
11894                    if *use_to {
11895                        self.write(" TO ");
11896                    } else {
11897                        self.write(" = ");
11898                    }
11899                    self.write(value);
11900                }
11901                FunctionSetValue::FromCurrent => {
11902                    self.write_space();
11903                    self.write_keyword("FROM CURRENT");
11904                }
11905            }
11906        }
11907        Ok(())
11908    }
11909
11910    /// Generate function body (AS clause)
11911    fn generate_function_body(&mut self, cf: &CreateFunction) -> Result<()> {
11912        if let Some(body) = &cf.body {
11913            // AS stays on same line as previous content (e.g., LANGUAGE js AS)
11914            self.write_space();
11915            // Only BigQuery uses multiline formatting for CREATE FUNCTION body
11916            let use_multiline = self.config.pretty
11917                && matches!(
11918                    self.config.dialect,
11919                    Some(crate::dialects::DialectType::BigQuery)
11920                );
11921            match body {
11922                FunctionBody::Block(block) => {
11923                    self.write_keyword("AS");
11924                    if matches!(
11925                        self.config.dialect,
11926                        Some(crate::dialects::DialectType::TSQL)
11927                    ) {
11928                        self.write(" BEGIN ");
11929                        self.write(block);
11930                        self.write(" END");
11931                    } else if matches!(
11932                        self.config.dialect,
11933                        Some(crate::dialects::DialectType::PostgreSQL)
11934                    ) {
11935                        self.write(" $$");
11936                        self.write(block);
11937                        self.write("$$");
11938                    } else {
11939                        // Escape content for single-quoted output
11940                        let escaped = self.escape_block_for_single_quote(block);
11941                        // In BigQuery pretty mode, body content goes on new line
11942                        if use_multiline {
11943                            self.write_newline();
11944                        } else {
11945                            self.write(" ");
11946                        }
11947                        self.write("'");
11948                        self.write(&escaped);
11949                        self.write("'");
11950                    }
11951                }
11952                FunctionBody::StringLiteral(s) => {
11953                    self.write_keyword("AS");
11954                    // In BigQuery pretty mode, body content goes on new line
11955                    if use_multiline {
11956                        self.write_newline();
11957                    } else {
11958                        self.write(" ");
11959                    }
11960                    self.write("'");
11961                    self.write(s);
11962                    self.write("'");
11963                }
11964                FunctionBody::Expression(expr) => {
11965                    self.write_keyword("AS");
11966                    self.write_space();
11967                    self.generate_expression(expr)?;
11968                }
11969                FunctionBody::External(name) => {
11970                    self.write_keyword("EXTERNAL NAME");
11971                    self.write(" '");
11972                    self.write(name);
11973                    self.write("'");
11974                }
11975                FunctionBody::Return(expr) => {
11976                    if matches!(
11977                        self.config.dialect,
11978                        Some(crate::dialects::DialectType::DuckDB)
11979                    ) {
11980                        // DuckDB macro syntax: AS [TABLE] expression (no RETURN keyword)
11981                        self.write_keyword("AS");
11982                        self.write_space();
11983                        // Empty returns_table_body signals TABLE return
11984                        if cf.returns_table_body.is_some() {
11985                            self.write_keyword("TABLE");
11986                            self.write_space();
11987                        }
11988                        self.generate_expression(expr)?;
11989                    } else {
11990                        if self.config.create_function_return_as {
11991                            self.write_keyword("AS");
11992                            // TSQL pretty: newline between AS and RETURN
11993                            if self.config.pretty
11994                                && matches!(
11995                                    self.config.dialect,
11996                                    Some(crate::dialects::DialectType::TSQL)
11997                                        | Some(crate::dialects::DialectType::Fabric)
11998                                )
11999                            {
12000                                self.write_newline();
12001                            } else {
12002                                self.write_space();
12003                            }
12004                        }
12005                        self.write_keyword("RETURN");
12006                        self.write_space();
12007                        self.generate_expression(expr)?;
12008                    }
12009                }
12010                FunctionBody::Statements(stmts) => {
12011                    self.write_keyword("AS");
12012                    self.write(" BEGIN ");
12013                    for (i, stmt) in stmts.iter().enumerate() {
12014                        if i > 0 {
12015                            self.write(" ");
12016                        }
12017                        self.generate_expression(stmt)?;
12018                    }
12019                    self.write(" END");
12020                }
12021                FunctionBody::DollarQuoted { content, tag } => {
12022                    self.write_keyword("AS");
12023                    self.write(" ");
12024                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
12025                    let supports_dollar_quoting = matches!(
12026                        self.config.dialect,
12027                        Some(crate::dialects::DialectType::PostgreSQL)
12028                            | Some(crate::dialects::DialectType::Databricks)
12029                            | Some(crate::dialects::DialectType::Redshift)
12030                            | Some(crate::dialects::DialectType::DuckDB)
12031                    );
12032                    if supports_dollar_quoting {
12033                        // Output in dollar-quoted format
12034                        self.write("$");
12035                        if let Some(t) = tag {
12036                            self.write(t);
12037                        }
12038                        self.write("$");
12039                        self.write(content);
12040                        self.write("$");
12041                        if let Some(t) = tag {
12042                            self.write(t);
12043                        }
12044                        self.write("$");
12045                    } else {
12046                        // Convert to single-quoted string for other dialects
12047                        let escaped = self.escape_block_for_single_quote(content);
12048                        self.write("'");
12049                        self.write(&escaped);
12050                        self.write("'");
12051                    }
12052                }
12053            }
12054        }
12055        Ok(())
12056    }
12057
12058    /// Generate determinism clause (IMMUTABLE/VOLATILE/DETERMINISTIC)
12059    fn generate_function_determinism(&mut self, cf: &CreateFunction) -> Result<()> {
12060        if let Some(det) = cf.deterministic {
12061            self.write_space();
12062            if matches!(
12063                self.config.dialect,
12064                Some(crate::dialects::DialectType::BigQuery)
12065            ) {
12066                // BigQuery uses DETERMINISTIC/NOT DETERMINISTIC
12067                if det {
12068                    self.write_keyword("DETERMINISTIC");
12069                } else {
12070                    self.write_keyword("NOT DETERMINISTIC");
12071                }
12072            } else {
12073                // PostgreSQL and others use IMMUTABLE/VOLATILE
12074                if det {
12075                    self.write_keyword("IMMUTABLE");
12076                } else {
12077                    self.write_keyword("VOLATILE");
12078                }
12079            }
12080        }
12081        Ok(())
12082    }
12083
12084    /// Generate null input handling clause
12085    fn generate_function_null_input(&mut self, cf: &CreateFunction) -> Result<()> {
12086        if let Some(returns_null) = cf.returns_null_on_null_input {
12087            self.write_space();
12088            if returns_null {
12089                if cf.strict {
12090                    self.write_keyword("STRICT");
12091                } else {
12092                    self.write_keyword("RETURNS NULL ON NULL INPUT");
12093                }
12094            } else {
12095                self.write_keyword("CALLED ON NULL INPUT");
12096            }
12097        }
12098        Ok(())
12099    }
12100
12101    /// Generate security clause
12102    fn generate_function_security(&mut self, cf: &CreateFunction) -> Result<()> {
12103        if let Some(security) = &cf.security {
12104            self.write_space();
12105            self.write_keyword("SECURITY");
12106            self.write_space();
12107            match security {
12108                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
12109                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
12110                FunctionSecurity::None => self.write_keyword("NONE"),
12111            }
12112        }
12113        Ok(())
12114    }
12115
12116    /// Generate SQL data access clause
12117    fn generate_function_sql_data_access(&mut self, cf: &CreateFunction) -> Result<()> {
12118        if let Some(sql_data) = &cf.sql_data_access {
12119            self.write_space();
12120            match sql_data {
12121                SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
12122                SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
12123                SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
12124                SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
12125            }
12126        }
12127        Ok(())
12128    }
12129
12130    fn generate_function_parameters(&mut self, params: &[FunctionParameter]) -> Result<()> {
12131        for (i, param) in params.iter().enumerate() {
12132            if i > 0 {
12133                self.write(", ");
12134            }
12135
12136            if let Some(mode) = &param.mode {
12137                if let Some(text) = &param.mode_text {
12138                    self.write(text);
12139                } else {
12140                    match mode {
12141                        ParameterMode::In => self.write_keyword("IN"),
12142                        ParameterMode::Out => self.write_keyword("OUT"),
12143                        ParameterMode::InOut => self.write_keyword("INOUT"),
12144                        ParameterMode::Variadic => self.write_keyword("VARIADIC"),
12145                    }
12146                }
12147                self.write_space();
12148            }
12149
12150            if let Some(name) = &param.name {
12151                self.generate_identifier(name)?;
12152                // Skip space and type for empty Custom types (e.g., DuckDB macros)
12153                let skip_type =
12154                    matches!(&param.data_type, DataType::Custom { name } if name.is_empty());
12155                if !skip_type {
12156                    self.write_space();
12157                    self.generate_data_type(&param.data_type)?;
12158                }
12159            } else {
12160                self.generate_data_type(&param.data_type)?;
12161            }
12162
12163            if let Some(default) = &param.default {
12164                if self.config.parameter_default_equals {
12165                    self.write(" = ");
12166                } else {
12167                    self.write(" DEFAULT ");
12168                }
12169                self.generate_expression(default)?;
12170            }
12171        }
12172
12173        Ok(())
12174    }
12175
12176    fn generate_drop_function(&mut self, df: &DropFunction) -> Result<()> {
12177        self.write_keyword("DROP FUNCTION");
12178
12179        if df.if_exists {
12180            self.write_space();
12181            self.write_keyword("IF EXISTS");
12182        }
12183
12184        self.write_space();
12185        self.generate_table(&df.name)?;
12186
12187        if let Some(params) = &df.parameters {
12188            self.write(" (");
12189            for (i, dt) in params.iter().enumerate() {
12190                if i > 0 {
12191                    self.write(", ");
12192                }
12193                self.generate_data_type(dt)?;
12194            }
12195            self.write(")");
12196        }
12197
12198        if df.cascade {
12199            self.write_space();
12200            self.write_keyword("CASCADE");
12201        }
12202
12203        Ok(())
12204    }
12205
12206    fn generate_create_procedure(&mut self, cp: &CreateProcedure) -> Result<()> {
12207        self.write_keyword("CREATE");
12208
12209        if cp.or_replace {
12210            self.write_space();
12211            self.write_keyword("OR REPLACE");
12212        }
12213
12214        self.write_space();
12215        if cp.use_proc_keyword {
12216            self.write_keyword("PROC");
12217        } else {
12218            self.write_keyword("PROCEDURE");
12219        }
12220
12221        if cp.if_not_exists {
12222            self.write_space();
12223            self.write_keyword("IF NOT EXISTS");
12224        }
12225
12226        self.write_space();
12227        self.generate_table(&cp.name)?;
12228        if cp.has_parens {
12229            self.write("(");
12230            self.generate_function_parameters(&cp.parameters)?;
12231            self.write(")");
12232        } else if !cp.parameters.is_empty() {
12233            // TSQL: unparenthesized parameters
12234            self.write_space();
12235            self.generate_function_parameters(&cp.parameters)?;
12236        }
12237
12238        // RETURNS clause (Snowflake)
12239        if let Some(return_type) = &cp.return_type {
12240            self.write_space();
12241            self.write_keyword("RETURNS");
12242            self.write_space();
12243            self.generate_data_type(return_type)?;
12244        }
12245
12246        // EXECUTE AS clause (Snowflake)
12247        if let Some(execute_as) = &cp.execute_as {
12248            self.write_space();
12249            self.write_keyword("EXECUTE AS");
12250            self.write_space();
12251            self.write_keyword(execute_as);
12252        }
12253
12254        if let Some(lang) = &cp.language {
12255            self.write_space();
12256            self.write_keyword("LANGUAGE");
12257            self.write_space();
12258            self.write(lang);
12259        }
12260
12261        if let Some(security) = &cp.security {
12262            self.write_space();
12263            self.write_keyword("SECURITY");
12264            self.write_space();
12265            match security {
12266                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
12267                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
12268                FunctionSecurity::None => self.write_keyword("NONE"),
12269            }
12270        }
12271
12272        // TSQL WITH options (ENCRYPTION, RECOMPILE, etc.)
12273        if !cp.with_options.is_empty() {
12274            self.write_space();
12275            self.write_keyword("WITH");
12276            self.write_space();
12277            for (i, opt) in cp.with_options.iter().enumerate() {
12278                if i > 0 {
12279                    self.write(", ");
12280                }
12281                self.write(opt);
12282            }
12283        }
12284
12285        if let Some(body) = &cp.body {
12286            self.write_space();
12287            match body {
12288                FunctionBody::Block(block) => {
12289                    self.write_keyword("AS");
12290                    if matches!(
12291                        self.config.dialect,
12292                        Some(crate::dialects::DialectType::TSQL)
12293                    ) {
12294                        self.write(" BEGIN ");
12295                        self.write(block);
12296                        self.write(" END");
12297                    } else if matches!(
12298                        self.config.dialect,
12299                        Some(crate::dialects::DialectType::PostgreSQL)
12300                    ) {
12301                        self.write(" $$");
12302                        self.write(block);
12303                        self.write("$$");
12304                    } else {
12305                        // Escape content for single-quoted output
12306                        let escaped = self.escape_block_for_single_quote(block);
12307                        self.write(" '");
12308                        self.write(&escaped);
12309                        self.write("'");
12310                    }
12311                }
12312                FunctionBody::StringLiteral(s) => {
12313                    self.write_keyword("AS");
12314                    self.write(" '");
12315                    self.write(s);
12316                    self.write("'");
12317                }
12318                FunctionBody::Expression(expr) => {
12319                    self.write_keyword("AS");
12320                    self.write_space();
12321                    self.generate_expression(expr)?;
12322                }
12323                FunctionBody::External(name) => {
12324                    self.write_keyword("EXTERNAL NAME");
12325                    self.write(" '");
12326                    self.write(name);
12327                    self.write("'");
12328                }
12329                FunctionBody::Return(expr) => {
12330                    self.write_keyword("RETURN");
12331                    self.write_space();
12332                    self.generate_expression(expr)?;
12333                }
12334                FunctionBody::Statements(stmts) => {
12335                    self.write_keyword("AS");
12336                    self.write(" BEGIN ");
12337                    for (i, stmt) in stmts.iter().enumerate() {
12338                        if i > 0 {
12339                            self.write(" ");
12340                        }
12341                        self.generate_expression(stmt)?;
12342                    }
12343                    self.write(" END");
12344                }
12345                FunctionBody::DollarQuoted { content, tag } => {
12346                    self.write_keyword("AS");
12347                    self.write(" ");
12348                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
12349                    let supports_dollar_quoting = matches!(
12350                        self.config.dialect,
12351                        Some(crate::dialects::DialectType::PostgreSQL)
12352                            | Some(crate::dialects::DialectType::Databricks)
12353                            | Some(crate::dialects::DialectType::Redshift)
12354                            | Some(crate::dialects::DialectType::DuckDB)
12355                    );
12356                    if supports_dollar_quoting {
12357                        // Output in dollar-quoted format
12358                        self.write("$");
12359                        if let Some(t) = tag {
12360                            self.write(t);
12361                        }
12362                        self.write("$");
12363                        self.write(content);
12364                        self.write("$");
12365                        if let Some(t) = tag {
12366                            self.write(t);
12367                        }
12368                        self.write("$");
12369                    } else {
12370                        // Convert to single-quoted string for other dialects
12371                        let escaped = self.escape_block_for_single_quote(content);
12372                        self.write("'");
12373                        self.write(&escaped);
12374                        self.write("'");
12375                    }
12376                }
12377            }
12378        }
12379
12380        Ok(())
12381    }
12382
12383    fn generate_drop_procedure(&mut self, dp: &DropProcedure) -> Result<()> {
12384        self.write_keyword("DROP PROCEDURE");
12385
12386        if dp.if_exists {
12387            self.write_space();
12388            self.write_keyword("IF EXISTS");
12389        }
12390
12391        self.write_space();
12392        self.generate_table(&dp.name)?;
12393
12394        if let Some(params) = &dp.parameters {
12395            self.write(" (");
12396            for (i, dt) in params.iter().enumerate() {
12397                if i > 0 {
12398                    self.write(", ");
12399                }
12400                self.generate_data_type(dt)?;
12401            }
12402            self.write(")");
12403        }
12404
12405        if dp.cascade {
12406            self.write_space();
12407            self.write_keyword("CASCADE");
12408        }
12409
12410        Ok(())
12411    }
12412
12413    fn generate_create_sequence(&mut self, cs: &CreateSequence) -> Result<()> {
12414        self.write_keyword("CREATE");
12415
12416        if cs.or_replace {
12417            self.write_space();
12418            self.write_keyword("OR REPLACE");
12419        }
12420
12421        if cs.temporary {
12422            self.write_space();
12423            self.write_keyword("TEMPORARY");
12424        }
12425
12426        self.write_space();
12427        self.write_keyword("SEQUENCE");
12428
12429        if cs.if_not_exists {
12430            self.write_space();
12431            self.write_keyword("IF NOT EXISTS");
12432        }
12433
12434        self.write_space();
12435        self.generate_table(&cs.name)?;
12436
12437        // Output AS <type> if present
12438        if let Some(as_type) = &cs.as_type {
12439            self.write_space();
12440            self.write_keyword("AS");
12441            self.write_space();
12442            self.generate_data_type(as_type)?;
12443        }
12444
12445        // Output COMMENT first (Snowflake convention: COMMENT comes before other properties)
12446        if let Some(comment) = &cs.comment {
12447            self.write_space();
12448            self.write_keyword("COMMENT");
12449            self.write("=");
12450            self.generate_string_literal(comment)?;
12451        }
12452
12453        // If property_order is available, use it to preserve original order
12454        if !cs.property_order.is_empty() {
12455            for prop in &cs.property_order {
12456                match prop {
12457                    SeqPropKind::Start => {
12458                        if let Some(start) = cs.start {
12459                            self.write_space();
12460                            self.write_keyword("START WITH");
12461                            self.write(&format!(" {}", start));
12462                        }
12463                    }
12464                    SeqPropKind::Increment => {
12465                        if let Some(inc) = cs.increment {
12466                            self.write_space();
12467                            self.write_keyword("INCREMENT BY");
12468                            self.write(&format!(" {}", inc));
12469                        }
12470                    }
12471                    SeqPropKind::Minvalue => {
12472                        if let Some(min) = &cs.minvalue {
12473                            self.write_space();
12474                            match min {
12475                                SequenceBound::Value(v) => {
12476                                    self.write_keyword("MINVALUE");
12477                                    self.write(&format!(" {}", v));
12478                                }
12479                                SequenceBound::None => {
12480                                    self.write_keyword("NO MINVALUE");
12481                                }
12482                            }
12483                        }
12484                    }
12485                    SeqPropKind::Maxvalue => {
12486                        if let Some(max) = &cs.maxvalue {
12487                            self.write_space();
12488                            match max {
12489                                SequenceBound::Value(v) => {
12490                                    self.write_keyword("MAXVALUE");
12491                                    self.write(&format!(" {}", v));
12492                                }
12493                                SequenceBound::None => {
12494                                    self.write_keyword("NO MAXVALUE");
12495                                }
12496                            }
12497                        }
12498                    }
12499                    SeqPropKind::Cache => {
12500                        if let Some(cache) = cs.cache {
12501                            self.write_space();
12502                            self.write_keyword("CACHE");
12503                            self.write(&format!(" {}", cache));
12504                        }
12505                    }
12506                    SeqPropKind::NoCache => {
12507                        self.write_space();
12508                        self.write_keyword("NO CACHE");
12509                    }
12510                    SeqPropKind::NoCacheWord => {
12511                        self.write_space();
12512                        self.write_keyword("NOCACHE");
12513                    }
12514                    SeqPropKind::Cycle => {
12515                        self.write_space();
12516                        self.write_keyword("CYCLE");
12517                    }
12518                    SeqPropKind::NoCycle => {
12519                        self.write_space();
12520                        self.write_keyword("NO CYCLE");
12521                    }
12522                    SeqPropKind::NoCycleWord => {
12523                        self.write_space();
12524                        self.write_keyword("NOCYCLE");
12525                    }
12526                    SeqPropKind::OwnedBy => {
12527                        // Skip OWNED BY NONE (it's a no-op)
12528                        if !cs.owned_by_none {
12529                            if let Some(owned) = &cs.owned_by {
12530                                self.write_space();
12531                                self.write_keyword("OWNED BY");
12532                                self.write_space();
12533                                self.generate_table(owned)?;
12534                            }
12535                        }
12536                    }
12537                    SeqPropKind::Order => {
12538                        self.write_space();
12539                        self.write_keyword("ORDER");
12540                    }
12541                    SeqPropKind::NoOrder => {
12542                        self.write_space();
12543                        self.write_keyword("NOORDER");
12544                    }
12545                    SeqPropKind::Comment => {
12546                        // COMMENT is output above, before property_order iteration
12547                    }
12548                    SeqPropKind::Sharing => {
12549                        if let Some(val) = &cs.sharing {
12550                            self.write_space();
12551                            self.write(&format!("SHARING={}", val));
12552                        }
12553                    }
12554                    SeqPropKind::Keep => {
12555                        self.write_space();
12556                        self.write_keyword("KEEP");
12557                    }
12558                    SeqPropKind::NoKeep => {
12559                        self.write_space();
12560                        self.write_keyword("NOKEEP");
12561                    }
12562                    SeqPropKind::Scale => {
12563                        self.write_space();
12564                        self.write_keyword("SCALE");
12565                        if let Some(modifier) = &cs.scale_modifier {
12566                            if !modifier.is_empty() {
12567                                self.write_space();
12568                                self.write_keyword(modifier);
12569                            }
12570                        }
12571                    }
12572                    SeqPropKind::NoScale => {
12573                        self.write_space();
12574                        self.write_keyword("NOSCALE");
12575                    }
12576                    SeqPropKind::Shard => {
12577                        self.write_space();
12578                        self.write_keyword("SHARD");
12579                        if let Some(modifier) = &cs.shard_modifier {
12580                            if !modifier.is_empty() {
12581                                self.write_space();
12582                                self.write_keyword(modifier);
12583                            }
12584                        }
12585                    }
12586                    SeqPropKind::NoShard => {
12587                        self.write_space();
12588                        self.write_keyword("NOSHARD");
12589                    }
12590                    SeqPropKind::Session => {
12591                        self.write_space();
12592                        self.write_keyword("SESSION");
12593                    }
12594                    SeqPropKind::Global => {
12595                        self.write_space();
12596                        self.write_keyword("GLOBAL");
12597                    }
12598                    SeqPropKind::NoMinvalueWord => {
12599                        self.write_space();
12600                        self.write_keyword("NOMINVALUE");
12601                    }
12602                    SeqPropKind::NoMaxvalueWord => {
12603                        self.write_space();
12604                        self.write_keyword("NOMAXVALUE");
12605                    }
12606                }
12607            }
12608        } else {
12609            // Fallback: default order for backwards compatibility
12610            if let Some(inc) = cs.increment {
12611                self.write_space();
12612                self.write_keyword("INCREMENT BY");
12613                self.write(&format!(" {}", inc));
12614            }
12615
12616            if let Some(min) = &cs.minvalue {
12617                self.write_space();
12618                match min {
12619                    SequenceBound::Value(v) => {
12620                        self.write_keyword("MINVALUE");
12621                        self.write(&format!(" {}", v));
12622                    }
12623                    SequenceBound::None => {
12624                        self.write_keyword("NO MINVALUE");
12625                    }
12626                }
12627            }
12628
12629            if let Some(max) = &cs.maxvalue {
12630                self.write_space();
12631                match max {
12632                    SequenceBound::Value(v) => {
12633                        self.write_keyword("MAXVALUE");
12634                        self.write(&format!(" {}", v));
12635                    }
12636                    SequenceBound::None => {
12637                        self.write_keyword("NO MAXVALUE");
12638                    }
12639                }
12640            }
12641
12642            if let Some(start) = cs.start {
12643                self.write_space();
12644                self.write_keyword("START WITH");
12645                self.write(&format!(" {}", start));
12646            }
12647
12648            if let Some(cache) = cs.cache {
12649                self.write_space();
12650                self.write_keyword("CACHE");
12651                self.write(&format!(" {}", cache));
12652            }
12653
12654            if cs.cycle {
12655                self.write_space();
12656                self.write_keyword("CYCLE");
12657            }
12658
12659            if let Some(owned) = &cs.owned_by {
12660                self.write_space();
12661                self.write_keyword("OWNED BY");
12662                self.write_space();
12663                self.generate_table(owned)?;
12664            }
12665        }
12666
12667        Ok(())
12668    }
12669
12670    fn generate_drop_sequence(&mut self, ds: &DropSequence) -> Result<()> {
12671        self.write_keyword("DROP SEQUENCE");
12672
12673        if ds.if_exists {
12674            self.write_space();
12675            self.write_keyword("IF EXISTS");
12676        }
12677
12678        self.write_space();
12679        self.generate_table(&ds.name)?;
12680
12681        if ds.cascade {
12682            self.write_space();
12683            self.write_keyword("CASCADE");
12684        }
12685
12686        Ok(())
12687    }
12688
12689    fn generate_alter_sequence(&mut self, als: &AlterSequence) -> Result<()> {
12690        self.write_keyword("ALTER SEQUENCE");
12691
12692        if als.if_exists {
12693            self.write_space();
12694            self.write_keyword("IF EXISTS");
12695        }
12696
12697        self.write_space();
12698        self.generate_table(&als.name)?;
12699
12700        if let Some(inc) = als.increment {
12701            self.write_space();
12702            self.write_keyword("INCREMENT BY");
12703            self.write(&format!(" {}", inc));
12704        }
12705
12706        if let Some(min) = &als.minvalue {
12707            self.write_space();
12708            match min {
12709                SequenceBound::Value(v) => {
12710                    self.write_keyword("MINVALUE");
12711                    self.write(&format!(" {}", v));
12712                }
12713                SequenceBound::None => {
12714                    self.write_keyword("NO MINVALUE");
12715                }
12716            }
12717        }
12718
12719        if let Some(max) = &als.maxvalue {
12720            self.write_space();
12721            match max {
12722                SequenceBound::Value(v) => {
12723                    self.write_keyword("MAXVALUE");
12724                    self.write(&format!(" {}", v));
12725                }
12726                SequenceBound::None => {
12727                    self.write_keyword("NO MAXVALUE");
12728                }
12729            }
12730        }
12731
12732        if let Some(start) = als.start {
12733            self.write_space();
12734            self.write_keyword("START WITH");
12735            self.write(&format!(" {}", start));
12736        }
12737
12738        if let Some(restart) = &als.restart {
12739            self.write_space();
12740            self.write_keyword("RESTART");
12741            if let Some(val) = restart {
12742                self.write_keyword(" WITH");
12743                self.write(&format!(" {}", val));
12744            }
12745        }
12746
12747        if let Some(cache) = als.cache {
12748            self.write_space();
12749            self.write_keyword("CACHE");
12750            self.write(&format!(" {}", cache));
12751        }
12752
12753        if let Some(cycle) = als.cycle {
12754            self.write_space();
12755            if cycle {
12756                self.write_keyword("CYCLE");
12757            } else {
12758                self.write_keyword("NO CYCLE");
12759            }
12760        }
12761
12762        if let Some(owned) = &als.owned_by {
12763            self.write_space();
12764            self.write_keyword("OWNED BY");
12765            self.write_space();
12766            if let Some(table) = owned {
12767                self.generate_table(table)?;
12768            } else {
12769                self.write_keyword("NONE");
12770            }
12771        }
12772
12773        Ok(())
12774    }
12775
12776    fn generate_create_trigger(&mut self, ct: &CreateTrigger) -> Result<()> {
12777        self.write_keyword("CREATE");
12778
12779        if ct.or_replace {
12780            self.write_space();
12781            self.write_keyword("OR REPLACE");
12782        }
12783
12784        if ct.constraint {
12785            self.write_space();
12786            self.write_keyword("CONSTRAINT");
12787        }
12788
12789        self.write_space();
12790        self.write_keyword("TRIGGER");
12791        self.write_space();
12792        self.generate_identifier(&ct.name)?;
12793
12794        self.write_space();
12795        match ct.timing {
12796            TriggerTiming::Before => self.write_keyword("BEFORE"),
12797            TriggerTiming::After => self.write_keyword("AFTER"),
12798            TriggerTiming::InsteadOf => self.write_keyword("INSTEAD OF"),
12799        }
12800
12801        // Events
12802        for (i, event) in ct.events.iter().enumerate() {
12803            if i > 0 {
12804                self.write_keyword(" OR");
12805            }
12806            self.write_space();
12807            match event {
12808                TriggerEvent::Insert => self.write_keyword("INSERT"),
12809                TriggerEvent::Update(cols) => {
12810                    self.write_keyword("UPDATE");
12811                    if let Some(cols) = cols {
12812                        self.write_space();
12813                        self.write_keyword("OF");
12814                        for (j, col) in cols.iter().enumerate() {
12815                            if j > 0 {
12816                                self.write(",");
12817                            }
12818                            self.write_space();
12819                            self.generate_identifier(col)?;
12820                        }
12821                    }
12822                }
12823                TriggerEvent::Delete => self.write_keyword("DELETE"),
12824                TriggerEvent::Truncate => self.write_keyword("TRUNCATE"),
12825            }
12826        }
12827
12828        self.write_space();
12829        self.write_keyword("ON");
12830        self.write_space();
12831        self.generate_table(&ct.table)?;
12832
12833        // Referencing clause
12834        if let Some(ref_clause) = &ct.referencing {
12835            self.write_space();
12836            self.write_keyword("REFERENCING");
12837            if let Some(old_table) = &ref_clause.old_table {
12838                self.write_space();
12839                self.write_keyword("OLD TABLE AS");
12840                self.write_space();
12841                self.generate_identifier(old_table)?;
12842            }
12843            if let Some(new_table) = &ref_clause.new_table {
12844                self.write_space();
12845                self.write_keyword("NEW TABLE AS");
12846                self.write_space();
12847                self.generate_identifier(new_table)?;
12848            }
12849            if let Some(old_row) = &ref_clause.old_row {
12850                self.write_space();
12851                self.write_keyword("OLD ROW AS");
12852                self.write_space();
12853                self.generate_identifier(old_row)?;
12854            }
12855            if let Some(new_row) = &ref_clause.new_row {
12856                self.write_space();
12857                self.write_keyword("NEW ROW AS");
12858                self.write_space();
12859                self.generate_identifier(new_row)?;
12860            }
12861        }
12862
12863        // Deferrable options for constraint triggers (must come before FOR EACH)
12864        if let Some(deferrable) = ct.deferrable {
12865            self.write_space();
12866            if deferrable {
12867                self.write_keyword("DEFERRABLE");
12868            } else {
12869                self.write_keyword("NOT DEFERRABLE");
12870            }
12871        }
12872
12873        if let Some(initially) = ct.initially_deferred {
12874            self.write_space();
12875            self.write_keyword("INITIALLY");
12876            self.write_space();
12877            if initially {
12878                self.write_keyword("DEFERRED");
12879            } else {
12880                self.write_keyword("IMMEDIATE");
12881            }
12882        }
12883
12884        self.write_space();
12885        self.write_keyword("FOR EACH");
12886        self.write_space();
12887        match ct.for_each {
12888            TriggerForEach::Row => self.write_keyword("ROW"),
12889            TriggerForEach::Statement => self.write_keyword("STATEMENT"),
12890        }
12891
12892        // When clause
12893        if let Some(when) = &ct.when {
12894            self.write_space();
12895            self.write_keyword("WHEN");
12896            self.write(" (");
12897            self.generate_expression(when)?;
12898            self.write(")");
12899        }
12900
12901        // Body
12902        self.write_space();
12903        match &ct.body {
12904            TriggerBody::Execute { function, args } => {
12905                self.write_keyword("EXECUTE FUNCTION");
12906                self.write_space();
12907                self.generate_table(function)?;
12908                self.write("(");
12909                for (i, arg) in args.iter().enumerate() {
12910                    if i > 0 {
12911                        self.write(", ");
12912                    }
12913                    self.generate_expression(arg)?;
12914                }
12915                self.write(")");
12916            }
12917            TriggerBody::Block(block) => {
12918                self.write_keyword("BEGIN");
12919                self.write_space();
12920                self.write(block);
12921                self.write_space();
12922                self.write_keyword("END");
12923            }
12924        }
12925
12926        Ok(())
12927    }
12928
12929    fn generate_drop_trigger(&mut self, dt: &DropTrigger) -> Result<()> {
12930        self.write_keyword("DROP TRIGGER");
12931
12932        if dt.if_exists {
12933            self.write_space();
12934            self.write_keyword("IF EXISTS");
12935        }
12936
12937        self.write_space();
12938        self.generate_identifier(&dt.name)?;
12939
12940        if let Some(table) = &dt.table {
12941            self.write_space();
12942            self.write_keyword("ON");
12943            self.write_space();
12944            self.generate_table(table)?;
12945        }
12946
12947        if dt.cascade {
12948            self.write_space();
12949            self.write_keyword("CASCADE");
12950        }
12951
12952        Ok(())
12953    }
12954
12955    fn generate_create_type(&mut self, ct: &CreateType) -> Result<()> {
12956        self.write_keyword("CREATE TYPE");
12957
12958        if ct.if_not_exists {
12959            self.write_space();
12960            self.write_keyword("IF NOT EXISTS");
12961        }
12962
12963        self.write_space();
12964        self.generate_table(&ct.name)?;
12965
12966        self.write_space();
12967        self.write_keyword("AS");
12968        self.write_space();
12969
12970        match &ct.definition {
12971            TypeDefinition::Enum(values) => {
12972                self.write_keyword("ENUM");
12973                self.write(" (");
12974                for (i, val) in values.iter().enumerate() {
12975                    if i > 0 {
12976                        self.write(", ");
12977                    }
12978                    self.write(&format!("'{}'", val));
12979                }
12980                self.write(")");
12981            }
12982            TypeDefinition::Composite(attrs) => {
12983                self.write("(");
12984                for (i, attr) in attrs.iter().enumerate() {
12985                    if i > 0 {
12986                        self.write(", ");
12987                    }
12988                    self.generate_identifier(&attr.name)?;
12989                    self.write_space();
12990                    self.generate_data_type(&attr.data_type)?;
12991                    if let Some(collate) = &attr.collate {
12992                        self.write_space();
12993                        self.write_keyword("COLLATE");
12994                        self.write_space();
12995                        self.generate_identifier(collate)?;
12996                    }
12997                }
12998                self.write(")");
12999            }
13000            TypeDefinition::Range {
13001                subtype,
13002                subtype_diff,
13003                canonical,
13004            } => {
13005                self.write_keyword("RANGE");
13006                self.write(" (");
13007                self.write_keyword("SUBTYPE");
13008                self.write(" = ");
13009                self.generate_data_type(subtype)?;
13010                if let Some(diff) = subtype_diff {
13011                    self.write(", ");
13012                    self.write_keyword("SUBTYPE_DIFF");
13013                    self.write(" = ");
13014                    self.write(diff);
13015                }
13016                if let Some(canon) = canonical {
13017                    self.write(", ");
13018                    self.write_keyword("CANONICAL");
13019                    self.write(" = ");
13020                    self.write(canon);
13021                }
13022                self.write(")");
13023            }
13024            TypeDefinition::Base {
13025                input,
13026                output,
13027                internallength,
13028            } => {
13029                self.write("(");
13030                self.write_keyword("INPUT");
13031                self.write(" = ");
13032                self.write(input);
13033                self.write(", ");
13034                self.write_keyword("OUTPUT");
13035                self.write(" = ");
13036                self.write(output);
13037                if let Some(len) = internallength {
13038                    self.write(", ");
13039                    self.write_keyword("INTERNALLENGTH");
13040                    self.write(" = ");
13041                    self.write(&len.to_string());
13042                }
13043                self.write(")");
13044            }
13045            TypeDefinition::Domain {
13046                base_type,
13047                default,
13048                constraints,
13049            } => {
13050                self.generate_data_type(base_type)?;
13051                if let Some(def) = default {
13052                    self.write_space();
13053                    self.write_keyword("DEFAULT");
13054                    self.write_space();
13055                    self.generate_expression(def)?;
13056                }
13057                for constr in constraints {
13058                    self.write_space();
13059                    if let Some(name) = &constr.name {
13060                        self.write_keyword("CONSTRAINT");
13061                        self.write_space();
13062                        self.generate_identifier(name)?;
13063                        self.write_space();
13064                    }
13065                    self.write_keyword("CHECK");
13066                    self.write(" (");
13067                    self.generate_expression(&constr.check)?;
13068                    self.write(")");
13069                }
13070            }
13071        }
13072
13073        Ok(())
13074    }
13075
13076    fn generate_drop_type(&mut self, dt: &DropType) -> Result<()> {
13077        self.write_keyword("DROP TYPE");
13078
13079        if dt.if_exists {
13080            self.write_space();
13081            self.write_keyword("IF EXISTS");
13082        }
13083
13084        self.write_space();
13085        self.generate_table(&dt.name)?;
13086
13087        if dt.cascade {
13088            self.write_space();
13089            self.write_keyword("CASCADE");
13090        }
13091
13092        Ok(())
13093    }
13094
13095    fn generate_describe(&mut self, d: &Describe) -> Result<()> {
13096        // Athena: DESCRIBE uses Hive engine (backticks)
13097        let saved_athena_hive_context = self.athena_hive_context;
13098        if matches!(
13099            self.config.dialect,
13100            Some(crate::dialects::DialectType::Athena)
13101        ) {
13102            self.athena_hive_context = true;
13103        }
13104
13105        // Output leading comments before DESCRIBE
13106        for comment in &d.leading_comments {
13107            self.write_formatted_comment(comment);
13108            self.write(" ");
13109        }
13110
13111        self.write_keyword("DESCRIBE");
13112
13113        if d.extended {
13114            self.write_space();
13115            self.write_keyword("EXTENDED");
13116        } else if d.formatted {
13117            self.write_space();
13118            self.write_keyword("FORMATTED");
13119        }
13120
13121        // Output style like ANALYZE, HISTORY
13122        if let Some(ref style) = d.style {
13123            self.write_space();
13124            self.write_keyword(style);
13125        }
13126
13127        // Handle object kind (TABLE, VIEW) based on dialect
13128        let should_output_kind = match self.config.dialect {
13129            // Spark doesn't use TABLE/VIEW after DESCRIBE
13130            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
13131                false
13132            }
13133            // Snowflake always includes TABLE
13134            Some(DialectType::Snowflake) => true,
13135            _ => d.kind.is_some(),
13136        };
13137        if should_output_kind {
13138            if let Some(ref kind) = d.kind {
13139                self.write_space();
13140                self.write_keyword(kind);
13141            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
13142                self.write_space();
13143                self.write_keyword("TABLE");
13144            }
13145        }
13146
13147        self.write_space();
13148        self.generate_expression(&d.target)?;
13149
13150        // Output PARTITION clause if present (the Partition expression outputs its own PARTITION keyword)
13151        if let Some(ref partition) = d.partition {
13152            self.write_space();
13153            self.generate_expression(partition)?;
13154        }
13155
13156        // Databricks: AS JSON
13157        if d.as_json {
13158            self.write_space();
13159            self.write_keyword("AS JSON");
13160        }
13161
13162        // Output properties like type=stage
13163        for (name, value) in &d.properties {
13164            self.write_space();
13165            self.write(name);
13166            self.write("=");
13167            self.write(value);
13168        }
13169
13170        // Restore Athena Hive context
13171        self.athena_hive_context = saved_athena_hive_context;
13172
13173        Ok(())
13174    }
13175
13176    /// Generate SHOW statement (Snowflake, MySQL, etc.)
13177    /// SHOW [TERSE] <object_type> [HISTORY] [LIKE pattern] [IN <scope>] [STARTS WITH pattern] [LIMIT n] [FROM object]
13178    fn generate_show(&mut self, s: &Show) -> Result<()> {
13179        self.write_keyword("SHOW");
13180        self.write_space();
13181
13182        // TERSE keyword - but not for PRIMARY KEYS, UNIQUE KEYS, IMPORTED KEYS
13183        // where TERSE is syntactically valid but has no effect on output
13184        let show_terse = s.terse
13185            && !matches!(
13186                s.this.as_str(),
13187                "PRIMARY KEYS" | "UNIQUE KEYS" | "IMPORTED KEYS"
13188            );
13189        if show_terse {
13190            self.write_keyword("TERSE");
13191            self.write_space();
13192        }
13193
13194        // Object type (USERS, TABLES, DATABASES, etc.)
13195        self.write_keyword(&s.this);
13196
13197        // Target identifier (MySQL: engine name in SHOW ENGINE, preserved case)
13198        if let Some(ref target_expr) = s.target {
13199            self.write_space();
13200            self.generate_expression(target_expr)?;
13201        }
13202
13203        // HISTORY keyword
13204        if s.history {
13205            self.write_space();
13206            self.write_keyword("HISTORY");
13207        }
13208
13209        // FOR target (MySQL: SHOW GRANTS FOR foo, SHOW PROFILE ... FOR QUERY 5)
13210        if let Some(ref for_target) = s.for_target {
13211            self.write_space();
13212            self.write_keyword("FOR");
13213            self.write_space();
13214            self.generate_expression(for_target)?;
13215        }
13216
13217        // Determine ordering based on dialect:
13218        // - Snowflake: LIKE, IN, STARTS WITH, LIMIT, FROM
13219        // - MySQL: IN, FROM, LIKE (when FROM is present)
13220        use crate::dialects::DialectType;
13221        let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
13222
13223        if !is_snowflake && s.from.is_some() {
13224            // MySQL ordering: IN, FROM, LIKE
13225
13226            // IN scope_kind [scope]
13227            if let Some(ref scope_kind) = s.scope_kind {
13228                self.write_space();
13229                self.write_keyword("IN");
13230                self.write_space();
13231                self.write_keyword(scope_kind);
13232                if let Some(ref scope) = s.scope {
13233                    self.write_space();
13234                    self.generate_expression(scope)?;
13235                }
13236            } else if let Some(ref scope) = s.scope {
13237                self.write_space();
13238                self.write_keyword("IN");
13239                self.write_space();
13240                self.generate_expression(scope)?;
13241            }
13242
13243            // FROM clause
13244            if let Some(ref from) = s.from {
13245                self.write_space();
13246                self.write_keyword("FROM");
13247                self.write_space();
13248                self.generate_expression(from)?;
13249            }
13250
13251            // Second FROM clause (db name)
13252            if let Some(ref db) = s.db {
13253                self.write_space();
13254                self.write_keyword("FROM");
13255                self.write_space();
13256                self.generate_expression(db)?;
13257            }
13258
13259            // LIKE pattern
13260            if let Some(ref like) = s.like {
13261                self.write_space();
13262                self.write_keyword("LIKE");
13263                self.write_space();
13264                self.generate_expression(like)?;
13265            }
13266        } else {
13267            // Snowflake ordering: LIKE, IN, STARTS WITH, LIMIT, FROM
13268
13269            // LIKE pattern
13270            if let Some(ref like) = s.like {
13271                self.write_space();
13272                self.write_keyword("LIKE");
13273                self.write_space();
13274                self.generate_expression(like)?;
13275            }
13276
13277            // IN scope_kind [scope]
13278            if let Some(ref scope_kind) = s.scope_kind {
13279                self.write_space();
13280                self.write_keyword("IN");
13281                self.write_space();
13282                self.write_keyword(scope_kind);
13283                if let Some(ref scope) = s.scope {
13284                    self.write_space();
13285                    self.generate_expression(scope)?;
13286                }
13287            } else if let Some(ref scope) = s.scope {
13288                self.write_space();
13289                self.write_keyword("IN");
13290                self.write_space();
13291                self.generate_expression(scope)?;
13292            }
13293        }
13294
13295        // STARTS WITH pattern
13296        if let Some(ref starts_with) = s.starts_with {
13297            self.write_space();
13298            self.write_keyword("STARTS WITH");
13299            self.write_space();
13300            self.generate_expression(starts_with)?;
13301        }
13302
13303        // LIMIT clause
13304        if let Some(ref limit) = s.limit {
13305            self.write_space();
13306            self.generate_limit(limit)?;
13307        }
13308
13309        // FROM clause (for Snowflake, FROM comes after STARTS WITH and LIMIT)
13310        if is_snowflake {
13311            if let Some(ref from) = s.from {
13312                self.write_space();
13313                self.write_keyword("FROM");
13314                self.write_space();
13315                self.generate_expression(from)?;
13316            }
13317        }
13318
13319        // WHERE clause (MySQL: SHOW STATUS WHERE condition)
13320        if let Some(ref where_clause) = s.where_clause {
13321            self.write_space();
13322            self.write_keyword("WHERE");
13323            self.write_space();
13324            self.generate_expression(where_clause)?;
13325        }
13326
13327        // MUTEX/STATUS suffix (MySQL: SHOW ENGINE foo STATUS/MUTEX)
13328        if let Some(is_mutex) = s.mutex {
13329            self.write_space();
13330            if is_mutex {
13331                self.write_keyword("MUTEX");
13332            } else {
13333                self.write_keyword("STATUS");
13334            }
13335        }
13336
13337        // WITH PRIVILEGES clause (Snowflake: SHOW ... WITH PRIVILEGES USAGE, MODIFY)
13338        if !s.privileges.is_empty() {
13339            self.write_space();
13340            self.write_keyword("WITH PRIVILEGES");
13341            self.write_space();
13342            for (i, priv_name) in s.privileges.iter().enumerate() {
13343                if i > 0 {
13344                    self.write(", ");
13345                }
13346                self.write_keyword(priv_name);
13347            }
13348        }
13349
13350        Ok(())
13351    }
13352
13353    // ==================== End DDL Generation ====================
13354
13355    fn generate_literal(&mut self, lit: &Literal) -> Result<()> {
13356        use crate::dialects::DialectType;
13357        match lit {
13358            Literal::String(s) => {
13359                self.generate_string_literal(s)?;
13360            }
13361            Literal::Number(n) => {
13362                if matches!(self.config.dialect, Some(DialectType::MySQL))
13363                    && n.len() > 2
13364                    && (n.starts_with("0x") || n.starts_with("0X"))
13365                    && !n[2..].chars().all(|c| c.is_ascii_hexdigit())
13366                {
13367                    return self.generate_identifier(&Identifier {
13368                        name: n.clone(),
13369                        quoted: true,
13370                        trailing_comments: Vec::new(),
13371                    });
13372                }
13373                // Normalize numbers starting with decimal point to have leading zero
13374                // e.g., .25 -> 0.25 (matches sqlglot behavior)
13375                if n.starts_with('.') {
13376                    self.write("0");
13377                    self.write(n);
13378                } else if n.starts_with("-.") {
13379                    // Handle negative numbers like -.25 -> -0.25
13380                    self.write("-0");
13381                    self.write(&n[1..]);
13382                } else {
13383                    self.write(n);
13384                }
13385            }
13386            Literal::HexString(h) => {
13387                // Most dialects use lowercase x'...' for hex literals; Spark/Databricks/Teradata use uppercase X'...'
13388                match self.config.dialect {
13389                    Some(DialectType::Spark)
13390                    | Some(DialectType::Databricks)
13391                    | Some(DialectType::Teradata) => self.write("X'"),
13392                    _ => self.write("x'"),
13393                }
13394                self.write(h);
13395                self.write("'");
13396            }
13397            Literal::HexNumber(h) => {
13398                // Hex number (0xA) - integer in hex notation (from BigQuery)
13399                // For BigQuery, TSQL, Fabric output as 0xHEX (native hex notation)
13400                // For other dialects, convert to decimal integer
13401                match self.config.dialect {
13402                    Some(DialectType::BigQuery)
13403                    | Some(DialectType::TSQL)
13404                    | Some(DialectType::Fabric) => {
13405                        self.write("0x");
13406                        self.write(h);
13407                    }
13408                    _ => {
13409                        // Convert hex to decimal
13410                        if let Ok(val) = u64::from_str_radix(h, 16) {
13411                            self.write(&val.to_string());
13412                        } else {
13413                            // Fallback: keep as 0x notation
13414                            self.write("0x");
13415                            self.write(h);
13416                        }
13417                    }
13418                }
13419            }
13420            Literal::BitString(b) => {
13421                // Bit string B'0101...'
13422                self.write("B'");
13423                self.write(b);
13424                self.write("'");
13425            }
13426            Literal::ByteString(b) => {
13427                // Byte string b'...' (BigQuery style)
13428                self.write("b'");
13429                // Escape special characters for output
13430                self.write_escaped_byte_string(b);
13431                self.write("'");
13432            }
13433            Literal::NationalString(s) => {
13434                // N'string' is supported by TSQL, Oracle, MySQL, and generic SQL
13435                // Other dialects strip the N prefix and output as regular string
13436                let keep_n_prefix = matches!(
13437                    self.config.dialect,
13438                    Some(DialectType::TSQL)
13439                        | Some(DialectType::Oracle)
13440                        | Some(DialectType::MySQL)
13441                        | None
13442                );
13443                if keep_n_prefix {
13444                    self.write("N'");
13445                } else {
13446                    self.write("'");
13447                }
13448                self.write(s);
13449                self.write("'");
13450            }
13451            Literal::Date(d) => {
13452                self.generate_date_literal(d)?;
13453            }
13454            Literal::Time(t) => {
13455                self.generate_time_literal(t)?;
13456            }
13457            Literal::Timestamp(ts) => {
13458                self.generate_timestamp_literal(ts)?;
13459            }
13460            Literal::Datetime(dt) => {
13461                self.generate_datetime_literal(dt)?;
13462            }
13463            Literal::TripleQuotedString(s, _quote_char) => {
13464                // For BigQuery and other dialects that don't support triple-quote, normalize to regular strings
13465                if matches!(
13466                    self.config.dialect,
13467                    Some(crate::dialects::DialectType::BigQuery)
13468                        | Some(crate::dialects::DialectType::DuckDB)
13469                        | Some(crate::dialects::DialectType::Snowflake)
13470                        | Some(crate::dialects::DialectType::Spark)
13471                        | Some(crate::dialects::DialectType::Hive)
13472                        | Some(crate::dialects::DialectType::Presto)
13473                        | Some(crate::dialects::DialectType::Trino)
13474                        | Some(crate::dialects::DialectType::PostgreSQL)
13475                        | Some(crate::dialects::DialectType::MySQL)
13476                        | Some(crate::dialects::DialectType::Redshift)
13477                        | Some(crate::dialects::DialectType::TSQL)
13478                        | Some(crate::dialects::DialectType::Oracle)
13479                        | Some(crate::dialects::DialectType::ClickHouse)
13480                        | Some(crate::dialects::DialectType::Databricks)
13481                        | Some(crate::dialects::DialectType::SQLite)
13482                ) {
13483                    self.generate_string_literal(s)?;
13484                } else {
13485                    // Preserve triple-quoted string syntax for generic/unknown dialects
13486                    let quotes = format!("{0}{0}{0}", _quote_char);
13487                    self.write(&quotes);
13488                    self.write(s);
13489                    self.write(&quotes);
13490                }
13491            }
13492            Literal::EscapeString(s) => {
13493                // PostgreSQL escape string: e'...' or E'...'
13494                // Token text format is "e:content" or "E:content"
13495                // Normalize escape sequences: \' -> '' (standard SQL doubled quote)
13496                use crate::dialects::DialectType;
13497                let content = if let Some(c) = s.strip_prefix("e:") {
13498                    c
13499                } else if let Some(c) = s.strip_prefix("E:") {
13500                    c
13501                } else {
13502                    s.as_str()
13503                };
13504
13505                // MySQL: output the content without quotes or prefix
13506                if matches!(
13507                    self.config.dialect,
13508                    Some(DialectType::MySQL) | Some(DialectType::TiDB)
13509                ) {
13510                    self.write(content);
13511                } else {
13512                    // Some dialects use lowercase e' prefix
13513                    let prefix = if matches!(
13514                        self.config.dialect,
13515                        Some(DialectType::SingleStore)
13516                            | Some(DialectType::DuckDB)
13517                            | Some(DialectType::PostgreSQL)
13518                            | Some(DialectType::CockroachDB)
13519                            | Some(DialectType::Materialize)
13520                            | Some(DialectType::RisingWave)
13521                    ) {
13522                        "e'"
13523                    } else {
13524                        "E'"
13525                    };
13526
13527                    // Normalize \' to '' for output
13528                    let normalized = content.replace("\\'", "''");
13529                    self.write(prefix);
13530                    self.write(&normalized);
13531                    self.write("'");
13532                }
13533            }
13534            Literal::DollarString(s) => {
13535                // Convert dollar-quoted strings to single-quoted strings
13536                // (like Python sqlglot's rawstring_sql)
13537                use crate::dialects::DialectType;
13538                // Extract content from tag\x00content format
13539                let (_tag, content) = crate::tokens::parse_dollar_string_token(s);
13540                // Step 1: Escape backslashes if the dialect uses backslash as a string escape
13541                let escape_backslash = matches!(self.config.dialect, Some(DialectType::Snowflake));
13542                // Step 2: Determine quote escaping style
13543                // Snowflake: ' -> \' (backslash escape)
13544                // PostgreSQL, DuckDB, others: ' -> '' (doubled quote)
13545                let use_backslash_quote =
13546                    matches!(self.config.dialect, Some(DialectType::Snowflake));
13547
13548                let mut escaped = String::with_capacity(content.len() + 4);
13549                for ch in content.chars() {
13550                    if escape_backslash && ch == '\\' {
13551                        // Escape backslash first (before quote escaping)
13552                        escaped.push('\\');
13553                        escaped.push('\\');
13554                    } else if ch == '\'' {
13555                        if use_backslash_quote {
13556                            escaped.push('\\');
13557                            escaped.push('\'');
13558                        } else {
13559                            escaped.push('\'');
13560                            escaped.push('\'');
13561                        }
13562                    } else {
13563                        escaped.push(ch);
13564                    }
13565                }
13566                self.write("'");
13567                self.write(&escaped);
13568                self.write("'");
13569            }
13570            Literal::RawString(s) => {
13571                // Raw strings (r"..." or r'...') contain literal backslashes.
13572                // When converting to a regular string, this follows Python sqlglot's rawstring_sql:
13573                // 1. If \\ is in STRING_ESCAPES, double all backslashes
13574                // 2. Apply ESCAPED_SEQUENCES for special chars (but NOT for backslash itself)
13575                // 3. Escape quotes using STRING_ESCAPES[0] + quote_char
13576                use crate::dialects::DialectType;
13577
13578                // Dialects where \\ is in STRING_ESCAPES (backslashes need doubling)
13579                let escape_backslash = matches!(
13580                    self.config.dialect,
13581                    Some(DialectType::BigQuery)
13582                        | Some(DialectType::MySQL)
13583                        | Some(DialectType::SingleStore)
13584                        | Some(DialectType::TiDB)
13585                        | Some(DialectType::Hive)
13586                        | Some(DialectType::Spark)
13587                        | Some(DialectType::Databricks)
13588                        | Some(DialectType::Drill)
13589                        | Some(DialectType::Snowflake)
13590                        | Some(DialectType::Redshift)
13591                        | Some(DialectType::ClickHouse)
13592                );
13593
13594                // Dialects where backslash is the PRIMARY string escape (STRING_ESCAPES[0] = "\\")
13595                // These escape quotes as \' instead of ''
13596                let backslash_escapes_quote = matches!(
13597                    self.config.dialect,
13598                    Some(DialectType::BigQuery)
13599                        | Some(DialectType::Hive)
13600                        | Some(DialectType::Spark)
13601                        | Some(DialectType::Databricks)
13602                        | Some(DialectType::Drill)
13603                        | Some(DialectType::Snowflake)
13604                        | Some(DialectType::Redshift)
13605                );
13606
13607                // Whether this dialect supports escaped sequences (ESCAPED_SEQUENCES mapping)
13608                // This is True when \\ is in STRING_ESCAPES (same as escape_backslash)
13609                let supports_escape_sequences = escape_backslash;
13610
13611                let mut escaped = String::with_capacity(s.len() + 4);
13612                for ch in s.chars() {
13613                    if escape_backslash && ch == '\\' {
13614                        // Double the backslash for the target dialect
13615                        escaped.push('\\');
13616                        escaped.push('\\');
13617                    } else if ch == '\'' {
13618                        if backslash_escapes_quote {
13619                            // Use backslash to escape the quote: \'
13620                            escaped.push('\\');
13621                            escaped.push('\'');
13622                        } else {
13623                            // Use SQL standard quote doubling: ''
13624                            escaped.push('\'');
13625                            escaped.push('\'');
13626                        }
13627                    } else if supports_escape_sequences {
13628                        // Apply ESCAPED_SEQUENCES mapping for special chars
13629                        // (escape_backslash=False in rawstring_sql, so \\ is NOT escaped here)
13630                        match ch {
13631                            '\n' => {
13632                                escaped.push('\\');
13633                                escaped.push('n');
13634                            }
13635                            '\r' => {
13636                                escaped.push('\\');
13637                                escaped.push('r');
13638                            }
13639                            '\t' => {
13640                                escaped.push('\\');
13641                                escaped.push('t');
13642                            }
13643                            '\x07' => {
13644                                escaped.push('\\');
13645                                escaped.push('a');
13646                            }
13647                            '\x08' => {
13648                                escaped.push('\\');
13649                                escaped.push('b');
13650                            }
13651                            '\x0C' => {
13652                                escaped.push('\\');
13653                                escaped.push('f');
13654                            }
13655                            '\x0B' => {
13656                                escaped.push('\\');
13657                                escaped.push('v');
13658                            }
13659                            _ => escaped.push(ch),
13660                        }
13661                    } else {
13662                        escaped.push(ch);
13663                    }
13664                }
13665                self.write("'");
13666                self.write(&escaped);
13667                self.write("'");
13668            }
13669        }
13670        Ok(())
13671    }
13672
13673    /// Generate a DATE literal with dialect-specific formatting
13674    fn generate_date_literal(&mut self, d: &str) -> Result<()> {
13675        use crate::dialects::DialectType;
13676
13677        match self.config.dialect {
13678            // SQL Server uses CONVERT or CAST
13679            Some(DialectType::TSQL) => {
13680                self.write("CAST('");
13681                self.write(d);
13682                self.write("' AS DATE)");
13683            }
13684            // BigQuery uses CAST syntax for type literals
13685            // DATE 'value' -> CAST('value' AS DATE)
13686            Some(DialectType::BigQuery) => {
13687                self.write("CAST('");
13688                self.write(d);
13689                self.write("' AS DATE)");
13690            }
13691            // Exasol uses CAST syntax for DATE literals
13692            // DATE 'value' -> CAST('value' AS DATE)
13693            Some(DialectType::Exasol) => {
13694                self.write("CAST('");
13695                self.write(d);
13696                self.write("' AS DATE)");
13697            }
13698            // Snowflake uses CAST syntax for DATE literals
13699            // DATE 'value' -> CAST('value' AS DATE)
13700            Some(DialectType::Snowflake) => {
13701                self.write("CAST('");
13702                self.write(d);
13703                self.write("' AS DATE)");
13704            }
13705            // PostgreSQL, MySQL, Redshift: DATE 'value' -> CAST('value' AS DATE)
13706            Some(DialectType::PostgreSQL)
13707            | Some(DialectType::MySQL)
13708            | Some(DialectType::SingleStore)
13709            | Some(DialectType::TiDB)
13710            | Some(DialectType::Redshift) => {
13711                self.write("CAST('");
13712                self.write(d);
13713                self.write("' AS DATE)");
13714            }
13715            // DuckDB, Presto, Trino, Spark: DATE 'value' -> CAST('value' AS DATE)
13716            Some(DialectType::DuckDB)
13717            | Some(DialectType::Presto)
13718            | Some(DialectType::Trino)
13719            | Some(DialectType::Athena)
13720            | Some(DialectType::Spark)
13721            | Some(DialectType::Databricks)
13722            | Some(DialectType::Hive) => {
13723                self.write("CAST('");
13724                self.write(d);
13725                self.write("' AS DATE)");
13726            }
13727            // Oracle: DATE 'value' -> TO_DATE('value', 'YYYY-MM-DD')
13728            Some(DialectType::Oracle) => {
13729                self.write("TO_DATE('");
13730                self.write(d);
13731                self.write("', 'YYYY-MM-DD')");
13732            }
13733            // Standard SQL: DATE '...'
13734            _ => {
13735                self.write_keyword("DATE");
13736                self.write(" '");
13737                self.write(d);
13738                self.write("'");
13739            }
13740        }
13741        Ok(())
13742    }
13743
13744    /// Generate a TIME literal with dialect-specific formatting
13745    fn generate_time_literal(&mut self, t: &str) -> Result<()> {
13746        use crate::dialects::DialectType;
13747
13748        match self.config.dialect {
13749            // SQL Server uses CONVERT or CAST
13750            Some(DialectType::TSQL) => {
13751                self.write("CAST('");
13752                self.write(t);
13753                self.write("' AS TIME)");
13754            }
13755            // Standard SQL: TIME '...'
13756            _ => {
13757                self.write_keyword("TIME");
13758                self.write(" '");
13759                self.write(t);
13760                self.write("'");
13761            }
13762        }
13763        Ok(())
13764    }
13765
13766    /// Generate a date expression for Dremio, converting DATE literals to CAST
13767    fn generate_dremio_date_expression(&mut self, expr: &Expression) -> Result<()> {
13768        use crate::expressions::Literal;
13769
13770        match expr {
13771            Expression::Literal(Literal::Date(d)) => {
13772                // DATE 'value' -> CAST('value' AS DATE)
13773                self.write("CAST('");
13774                self.write(d);
13775                self.write("' AS DATE)");
13776            }
13777            _ => {
13778                // For all other expressions, generate normally
13779                self.generate_expression(expr)?;
13780            }
13781        }
13782        Ok(())
13783    }
13784
13785    /// Generate a TIMESTAMP literal with dialect-specific formatting
13786    fn generate_timestamp_literal(&mut self, ts: &str) -> Result<()> {
13787        use crate::dialects::DialectType;
13788
13789        match self.config.dialect {
13790            // SQL Server uses CONVERT or CAST
13791            Some(DialectType::TSQL) => {
13792                self.write("CAST('");
13793                self.write(ts);
13794                self.write("' AS DATETIME2)");
13795            }
13796            // BigQuery uses CAST syntax for type literals
13797            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
13798            Some(DialectType::BigQuery) => {
13799                self.write("CAST('");
13800                self.write(ts);
13801                self.write("' AS TIMESTAMP)");
13802            }
13803            // Snowflake uses CAST syntax for TIMESTAMP literals
13804            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
13805            Some(DialectType::Snowflake) => {
13806                self.write("CAST('");
13807                self.write(ts);
13808                self.write("' AS TIMESTAMP)");
13809            }
13810            // Dremio uses CAST syntax for TIMESTAMP literals
13811            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
13812            Some(DialectType::Dremio) => {
13813                self.write("CAST('");
13814                self.write(ts);
13815                self.write("' AS TIMESTAMP)");
13816            }
13817            // Exasol uses CAST syntax for TIMESTAMP literals
13818            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
13819            Some(DialectType::Exasol) => {
13820                self.write("CAST('");
13821                self.write(ts);
13822                self.write("' AS TIMESTAMP)");
13823            }
13824            // Oracle prefers TO_TIMESTAMP function call
13825            // TIMESTAMP 'value' -> TO_TIMESTAMP('value', 'YYYY-MM-DD HH24:MI:SS.FF6')
13826            Some(DialectType::Oracle) => {
13827                self.write("TO_TIMESTAMP('");
13828                self.write(ts);
13829                self.write("', 'YYYY-MM-DD HH24:MI:SS.FF6')");
13830            }
13831            // Presto/Trino: always use CAST for TIMESTAMP literals
13832            Some(DialectType::Presto) | Some(DialectType::Trino) => {
13833                if Self::timestamp_has_timezone(ts) {
13834                    self.write("CAST('");
13835                    self.write(ts);
13836                    self.write("' AS TIMESTAMP WITH TIME ZONE)");
13837                } else {
13838                    self.write("CAST('");
13839                    self.write(ts);
13840                    self.write("' AS TIMESTAMP)");
13841                }
13842            }
13843            // ClickHouse: CAST('...' AS Nullable(DateTime))
13844            Some(DialectType::ClickHouse) => {
13845                self.write("CAST('");
13846                self.write(ts);
13847                self.write("' AS Nullable(DateTime))");
13848            }
13849            // Spark: CAST('...' AS TIMESTAMP)
13850            Some(DialectType::Spark) => {
13851                self.write("CAST('");
13852                self.write(ts);
13853                self.write("' AS TIMESTAMP)");
13854            }
13855            // Redshift: CAST('...' AS TIMESTAMP) for regular timestamps,
13856            // but TIMESTAMP '...' for special values like 'epoch'
13857            Some(DialectType::Redshift) => {
13858                if ts == "epoch" {
13859                    self.write_keyword("TIMESTAMP");
13860                    self.write(" '");
13861                    self.write(ts);
13862                    self.write("'");
13863                } else {
13864                    self.write("CAST('");
13865                    self.write(ts);
13866                    self.write("' AS TIMESTAMP)");
13867                }
13868            }
13869            // PostgreSQL, Hive, DuckDB, etc.: CAST('...' AS TIMESTAMP)
13870            Some(DialectType::PostgreSQL)
13871            | Some(DialectType::Hive)
13872            | Some(DialectType::SQLite)
13873            | Some(DialectType::DuckDB)
13874            | Some(DialectType::Athena)
13875            | Some(DialectType::Drill)
13876            | Some(DialectType::Teradata) => {
13877                self.write("CAST('");
13878                self.write(ts);
13879                self.write("' AS TIMESTAMP)");
13880            }
13881            // MySQL/StarRocks: CAST('...' AS DATETIME)
13882            Some(DialectType::MySQL) | Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
13883                self.write("CAST('");
13884                self.write(ts);
13885                self.write("' AS DATETIME)");
13886            }
13887            // Databricks: CAST('...' AS TIMESTAMP_NTZ)
13888            Some(DialectType::Databricks) => {
13889                self.write("CAST('");
13890                self.write(ts);
13891                self.write("' AS TIMESTAMP_NTZ)");
13892            }
13893            // Standard SQL: TIMESTAMP '...'
13894            _ => {
13895                self.write_keyword("TIMESTAMP");
13896                self.write(" '");
13897                self.write(ts);
13898                self.write("'");
13899            }
13900        }
13901        Ok(())
13902    }
13903
13904    /// Check if a timestamp string contains a timezone identifier
13905    /// This detects IANA timezone names like Europe/Prague, America/New_York, etc.
13906    fn timestamp_has_timezone(ts: &str) -> bool {
13907        // Check for common IANA timezone patterns: Continent/City format
13908        // Examples: Europe/Prague, America/New_York, Asia/Tokyo, etc.
13909        // Also handles: UTC, GMT, Etc/GMT+0, etc.
13910        let ts_lower = ts.to_lowercase();
13911
13912        // Check for Continent/City pattern (most common)
13913        let continent_prefixes = [
13914            "africa/",
13915            "america/",
13916            "antarctica/",
13917            "arctic/",
13918            "asia/",
13919            "atlantic/",
13920            "australia/",
13921            "europe/",
13922            "indian/",
13923            "pacific/",
13924            "etc/",
13925            "brazil/",
13926            "canada/",
13927            "chile/",
13928            "mexico/",
13929            "us/",
13930        ];
13931
13932        for prefix in &continent_prefixes {
13933            if ts_lower.contains(prefix) {
13934                return true;
13935            }
13936        }
13937
13938        // Check for standalone timezone abbreviations at the end
13939        // These typically appear after the time portion
13940        let tz_abbrevs = [
13941            " utc", " gmt", " cet", " cest", " eet", " eest", " wet", " west", " est", " edt",
13942            " cst", " cdt", " mst", " mdt", " pst", " pdt", " ist", " bst", " jst", " kst", " hkt",
13943            " sgt", " aest", " aedt", " acst", " acdt", " awst",
13944        ];
13945
13946        for abbrev in &tz_abbrevs {
13947            if ts_lower.ends_with(abbrev) {
13948                return true;
13949            }
13950        }
13951
13952        // Check for numeric timezone offsets: +N, -N, +NN:NN, -NN:NN
13953        // Examples: "2012-10-31 01:00 -2", "2012-10-31 01:00 +02:00"
13954        // Look for pattern: space followed by + or - and digits (optionally with :)
13955        let trimmed = ts.trim();
13956        if let Some(last_space) = trimmed.rfind(' ') {
13957            let suffix = &trimmed[last_space + 1..];
13958            if (suffix.starts_with('+') || suffix.starts_with('-')) && suffix.len() > 1 {
13959                // Check if rest is numeric (possibly with : for hh:mm format)
13960                let rest = &suffix[1..];
13961                if rest.chars().all(|c| c.is_ascii_digit() || c == ':') {
13962                    return true;
13963                }
13964            }
13965        }
13966
13967        false
13968    }
13969
13970    /// Generate a DATETIME literal with dialect-specific formatting
13971    fn generate_datetime_literal(&mut self, dt: &str) -> Result<()> {
13972        use crate::dialects::DialectType;
13973
13974        match self.config.dialect {
13975            // BigQuery uses CAST syntax for type literals
13976            // DATETIME 'value' -> CAST('value' AS DATETIME)
13977            Some(DialectType::BigQuery) => {
13978                self.write("CAST('");
13979                self.write(dt);
13980                self.write("' AS DATETIME)");
13981            }
13982            // DuckDB: DATETIME -> CAST('value' AS TIMESTAMP)
13983            Some(DialectType::DuckDB) => {
13984                self.write("CAST('");
13985                self.write(dt);
13986                self.write("' AS TIMESTAMP)");
13987            }
13988            // DATETIME is primarily a BigQuery type
13989            // Output as DATETIME '...' for dialects that support it
13990            _ => {
13991                self.write_keyword("DATETIME");
13992                self.write(" '");
13993                self.write(dt);
13994                self.write("'");
13995            }
13996        }
13997        Ok(())
13998    }
13999
14000    /// Generate a string literal with dialect-specific escaping
14001    fn generate_string_literal(&mut self, s: &str) -> Result<()> {
14002        use crate::dialects::DialectType;
14003
14004        match self.config.dialect {
14005            // MySQL/Hive: Uses SQL standard quote escaping ('') for quotes,
14006            // and backslash escaping for special characters like newlines
14007            // Hive STRING_ESCAPES = ["\\"] - uses backslash escapes
14008            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
14009                // Hive/Spark use backslash escaping for quotes (\') and special chars
14010                self.write("'");
14011                for c in s.chars() {
14012                    match c {
14013                        '\'' => self.write("\\'"),
14014                        '\\' => self.write("\\\\"),
14015                        '\n' => self.write("\\n"),
14016                        '\r' => self.write("\\r"),
14017                        '\t' => self.write("\\t"),
14018                        '\0' => self.write("\\0"),
14019                        _ => self.output.push(c),
14020                    }
14021                }
14022                self.write("'");
14023            }
14024            Some(DialectType::Drill) => {
14025                // Drill uses SQL-standard quote doubling ('') for quotes,
14026                // but backslash escaping for special characters
14027                self.write("'");
14028                for c in s.chars() {
14029                    match c {
14030                        '\'' => self.write("''"),
14031                        '\\' => self.write("\\\\"),
14032                        '\n' => self.write("\\n"),
14033                        '\r' => self.write("\\r"),
14034                        '\t' => self.write("\\t"),
14035                        '\0' => self.write("\\0"),
14036                        _ => self.output.push(c),
14037                    }
14038                }
14039                self.write("'");
14040            }
14041            Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB) => {
14042                self.write("'");
14043                for c in s.chars() {
14044                    match c {
14045                        // MySQL uses SQL standard quote doubling
14046                        '\'' => self.write("''"),
14047                        '\\' => self.write("\\\\"),
14048                        '\n' => self.write("\\n"),
14049                        '\r' => self.write("\\r"),
14050                        '\t' => self.write("\\t"),
14051                        // sqlglot writes a literal NUL for this case
14052                        '\0' => self.output.push('\0'),
14053                        _ => self.output.push(c),
14054                    }
14055                }
14056                self.write("'");
14057            }
14058            // BigQuery: Uses backslash escaping
14059            Some(DialectType::BigQuery) => {
14060                self.write("'");
14061                for c in s.chars() {
14062                    match c {
14063                        '\'' => self.write("\\'"),
14064                        '\\' => self.write("\\\\"),
14065                        '\n' => self.write("\\n"),
14066                        '\r' => self.write("\\r"),
14067                        '\t' => self.write("\\t"),
14068                        '\0' => self.write("\\0"),
14069                        '\x07' => self.write("\\a"),
14070                        '\x08' => self.write("\\b"),
14071                        '\x0C' => self.write("\\f"),
14072                        '\x0B' => self.write("\\v"),
14073                        _ => self.output.push(c),
14074                    }
14075                }
14076                self.write("'");
14077            }
14078            // Athena: Uses different escaping for DDL (Hive) vs DML (Trino)
14079            // In Hive context (DDL): backslash escaping for single quotes (\') and backslashes (\\)
14080            // In Trino context (DML): SQL-standard escaping ('') and literal backslashes
14081            Some(DialectType::Athena) => {
14082                if self.athena_hive_context {
14083                    // Hive-style: backslash escaping
14084                    self.write("'");
14085                    for c in s.chars() {
14086                        match c {
14087                            '\'' => self.write("\\'"),
14088                            '\\' => self.write("\\\\"),
14089                            '\n' => self.write("\\n"),
14090                            '\r' => self.write("\\r"),
14091                            '\t' => self.write("\\t"),
14092                            '\0' => self.write("\\0"),
14093                            _ => self.output.push(c),
14094                        }
14095                    }
14096                    self.write("'");
14097                } else {
14098                    // Trino-style: SQL-standard escaping, preserve backslashes
14099                    self.write("'");
14100                    for c in s.chars() {
14101                        match c {
14102                            '\'' => self.write("''"),
14103                            // Preserve backslashes literally (no re-escaping)
14104                            _ => self.output.push(c),
14105                        }
14106                    }
14107                    self.write("'");
14108                }
14109            }
14110            // Snowflake: Uses backslash escaping (STRING_ESCAPES = ["\\", "'"])
14111            // The tokenizer preserves backslash escape sequences literally (e.g., input '\\'
14112            // becomes string value '\\'), so we should NOT re-escape backslashes.
14113            // We only need to escape single quotes.
14114            Some(DialectType::Snowflake) => {
14115                self.write("'");
14116                for c in s.chars() {
14117                    match c {
14118                        '\'' => self.write("\\'"),
14119                        // Backslashes are already escaped in the tokenized string, don't re-escape
14120                        // Only escape special characters that might not have been escaped
14121                        '\n' => self.write("\\n"),
14122                        '\r' => self.write("\\r"),
14123                        '\t' => self.write("\\t"),
14124                        _ => self.output.push(c),
14125                    }
14126                }
14127                self.write("'");
14128            }
14129            // PostgreSQL: Output special characters as literal chars in strings (no E-string prefix)
14130            Some(DialectType::PostgreSQL) => {
14131                self.write("'");
14132                for c in s.chars() {
14133                    match c {
14134                        '\'' => self.write("''"),
14135                        _ => self.output.push(c),
14136                    }
14137                }
14138                self.write("'");
14139            }
14140            // Redshift: Uses backslash escaping for single quotes
14141            Some(DialectType::Redshift) => {
14142                self.write("'");
14143                for c in s.chars() {
14144                    match c {
14145                        '\'' => self.write("\\'"),
14146                        _ => self.output.push(c),
14147                    }
14148                }
14149                self.write("'");
14150            }
14151            // Oracle: Uses standard double single-quote escaping
14152            Some(DialectType::Oracle) => {
14153                self.write("'");
14154                self.write(&s.replace('\'', "''"));
14155                self.write("'");
14156            }
14157            // ClickHouse: Uses SQL-standard quote doubling ('') for quotes,
14158            // backslash escaping for backslashes and special characters
14159            Some(DialectType::ClickHouse) => {
14160                self.write("'");
14161                for c in s.chars() {
14162                    match c {
14163                        '\'' => self.write("''"),
14164                        '\\' => self.write("\\\\"),
14165                        '\n' => self.write("\\n"),
14166                        '\r' => self.write("\\r"),
14167                        '\t' => self.write("\\t"),
14168                        '\0' => self.write("\\0"),
14169                        _ => self.output.push(c),
14170                    }
14171                }
14172                self.write("'");
14173            }
14174            // Default: SQL standard double single quotes (works for most dialects)
14175            // PostgreSQL, Snowflake, DuckDB, TSQL, etc.
14176            _ => {
14177                self.write("'");
14178                self.write(&s.replace('\'', "''"));
14179                self.write("'");
14180            }
14181        }
14182        Ok(())
14183    }
14184
14185    /// Write a byte string with proper escaping for BigQuery-style byte literals
14186    /// Escapes characters as \xNN hex escapes where needed
14187    fn write_escaped_byte_string(&mut self, s: &str) {
14188        for c in s.chars() {
14189            match c {
14190                // Escape single quotes
14191                '\'' => self.write("\\'"),
14192                // Escape backslashes
14193                '\\' => self.write("\\\\"),
14194                // Keep all printable characters (including non-ASCII) as-is
14195                _ if !c.is_control() => self.output.push(c),
14196                // Escape control characters as hex
14197                _ => {
14198                    let byte = c as u32;
14199                    if byte < 256 {
14200                        self.write(&format!("\\x{:02x}", byte));
14201                    } else {
14202                        // For unicode characters, write each UTF-8 byte
14203                        for b in c.to_string().as_bytes() {
14204                            self.write(&format!("\\x{:02x}", b));
14205                        }
14206                    }
14207                }
14208            }
14209        }
14210    }
14211
14212    fn generate_boolean(&mut self, b: &BooleanLiteral) -> Result<()> {
14213        use crate::dialects::DialectType;
14214
14215        // Different dialects have different boolean literal formats
14216        match self.config.dialect {
14217            // SQL Server typically uses 1/0 for boolean literals in many contexts
14218            // However, TRUE/FALSE also works in modern versions
14219            Some(DialectType::TSQL) => {
14220                self.write(if b.value { "1" } else { "0" });
14221            }
14222            // Oracle traditionally uses 1/0 (no native boolean until recent versions)
14223            Some(DialectType::Oracle) => {
14224                self.write(if b.value { "1" } else { "0" });
14225            }
14226            // MySQL accepts TRUE/FALSE as aliases for 1/0
14227            Some(DialectType::MySQL) => {
14228                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
14229            }
14230            // Most other dialects support TRUE/FALSE
14231            _ => {
14232                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
14233            }
14234        }
14235        Ok(())
14236    }
14237
14238    /// Generate an identifier that's used as an alias name
14239    /// This quotes reserved keywords in addition to already-quoted identifiers
14240    fn generate_alias_identifier(&mut self, id: &Identifier) -> Result<()> {
14241        let name = &id.name;
14242        let quote_style = &self.config.identifier_quote_style;
14243
14244        // For aliases, quote if:
14245        // 1. The identifier was explicitly quoted in the source
14246        // 2. The identifier is a reserved keyword for the current dialect
14247        let needs_quoting = id.quoted || self.is_reserved_keyword(name);
14248
14249        // Normalize identifier if configured
14250        let output_name = if self.config.normalize_identifiers && !id.quoted {
14251            name.to_lowercase()
14252        } else {
14253            name.to_string()
14254        };
14255
14256        if needs_quoting {
14257            // Escape any quote characters within the identifier
14258            let escaped_name = if quote_style.start == quote_style.end {
14259                output_name.replace(
14260                    quote_style.end,
14261                    &format!("{}{}", quote_style.end, quote_style.end),
14262                )
14263            } else {
14264                output_name.replace(
14265                    quote_style.end,
14266                    &format!("{}{}", quote_style.end, quote_style.end),
14267                )
14268            };
14269            self.write(&format!(
14270                "{}{}{}",
14271                quote_style.start, escaped_name, quote_style.end
14272            ));
14273        } else {
14274            self.write(&output_name);
14275        }
14276
14277        // Output trailing comments
14278        for comment in &id.trailing_comments {
14279            self.write(" ");
14280            self.write_formatted_comment(comment);
14281        }
14282        Ok(())
14283    }
14284
14285    fn generate_identifier(&mut self, id: &Identifier) -> Result<()> {
14286        use crate::dialects::DialectType;
14287
14288        let name = &id.name;
14289
14290        // For Athena, use backticks in Hive context, double quotes in Trino context
14291        let quote_style = if matches!(self.config.dialect, Some(DialectType::Athena))
14292            && self.athena_hive_context
14293        {
14294            &IdentifierQuoteStyle::BACKTICK
14295        } else {
14296            &self.config.identifier_quote_style
14297        };
14298
14299        // Quote if:
14300        // 1. The identifier was explicitly quoted in the source
14301        // 2. The identifier is a reserved keyword for the current dialect
14302        // 3. The config says to always quote identifiers (e.g., Athena/Presto)
14303        // This matches Python sqlglot's identifier_sql behavior
14304        // Also quote identifiers starting with digits if the target dialect doesn't support them
14305        let starts_with_digit = name.chars().next().map_or(false, |c| c.is_ascii_digit());
14306        let needs_digit_quoting = starts_with_digit
14307            && !self.config.identifiers_can_start_with_digit
14308            && self.config.dialect.is_some();
14309        let mysql_invalid_hex_identifier = matches!(self.config.dialect, Some(DialectType::MySQL))
14310            && name.len() > 2
14311            && (name.starts_with("0x") || name.starts_with("0X"))
14312            && !name[2..].chars().all(|c| c.is_ascii_hexdigit());
14313        let needs_quoting = id.quoted
14314            || self.is_reserved_keyword(name)
14315            || self.config.always_quote_identifiers
14316            || needs_digit_quoting
14317            || mysql_invalid_hex_identifier;
14318
14319        // Check for MySQL index column prefix length: name(16) or name(16) ASC/DESC
14320        // When quoted, we need to output `name`(16) not `name(16)`
14321        let (base_name, suffix) = if needs_quoting {
14322            // Try to extract prefix length from identifier: name(number) or name(number) ASC/DESC
14323            if let Some(paren_pos) = name.find('(') {
14324                let base = &name[..paren_pos];
14325                let rest = &name[paren_pos..];
14326                // Verify it looks like (digits) or (digits) ASC/DESC
14327                if rest.starts_with('(')
14328                    && (rest.ends_with(')') || rest.ends_with(") ASC") || rest.ends_with(") DESC"))
14329                {
14330                    // Check if content between parens is all digits
14331                    let close_paren = rest.find(')').unwrap_or(rest.len());
14332                    let inside = &rest[1..close_paren];
14333                    if inside.chars().all(|c| c.is_ascii_digit()) {
14334                        (base.to_string(), rest.to_string())
14335                    } else {
14336                        (name.to_string(), String::new())
14337                    }
14338                } else {
14339                    (name.to_string(), String::new())
14340                }
14341            } else if name.ends_with(" ASC") {
14342                let base = &name[..name.len() - 4];
14343                (base.to_string(), " ASC".to_string())
14344            } else if name.ends_with(" DESC") {
14345                let base = &name[..name.len() - 5];
14346                (base.to_string(), " DESC".to_string())
14347            } else {
14348                (name.to_string(), String::new())
14349            }
14350        } else {
14351            (name.to_string(), String::new())
14352        };
14353
14354        // Normalize identifier if configured, with special handling for Exasol
14355        // Exasol uses UPPERCASE normalization strategy, so reserved keywords that need quoting
14356        // should be uppercased when not already quoted (to match Python sqlglot behavior)
14357        let output_name = if self.config.normalize_identifiers && !id.quoted {
14358            base_name.to_lowercase()
14359        } else if matches!(self.config.dialect, Some(DialectType::Exasol))
14360            && !id.quoted
14361            && self.is_reserved_keyword(name)
14362        {
14363            // Exasol: uppercase reserved keywords when quoting them
14364            // This matches Python sqlglot's behavior with NORMALIZATION_STRATEGY = UPPERCASE
14365            base_name.to_uppercase()
14366        } else {
14367            base_name
14368        };
14369
14370        if needs_quoting {
14371            // Escape any quote characters within the identifier
14372            let escaped_name = if quote_style.start == quote_style.end {
14373                // Same start/end char (e.g., " or `) - double the quote char
14374                output_name.replace(
14375                    quote_style.end,
14376                    &format!("{}{}", quote_style.end, quote_style.end),
14377                )
14378            } else {
14379                // Different start/end (e.g., [ and ]) - escape only the end char
14380                output_name.replace(
14381                    quote_style.end,
14382                    &format!("{}{}", quote_style.end, quote_style.end),
14383                )
14384            };
14385            self.write(&format!(
14386                "{}{}{}{}",
14387                quote_style.start, escaped_name, quote_style.end, suffix
14388            ));
14389        } else {
14390            self.write(&output_name);
14391        }
14392
14393        // Output trailing comments
14394        for comment in &id.trailing_comments {
14395            self.write(" ");
14396            self.write_formatted_comment(comment);
14397        }
14398        Ok(())
14399    }
14400
14401    fn generate_column(&mut self, col: &Column) -> Result<()> {
14402        use crate::dialects::DialectType;
14403
14404        if let Some(table) = &col.table {
14405            // Exasol special case: LOCAL as column table prefix should NOT be quoted
14406            // LOCAL is a special keyword in Exasol for referencing aliases from the current scope
14407            // Only applies when: dialect is Exasol, name is "LOCAL" (case-insensitive), and not already quoted
14408            let is_exasol_local_prefix = matches!(self.config.dialect, Some(DialectType::Exasol))
14409                && !table.quoted
14410                && table.name.eq_ignore_ascii_case("LOCAL");
14411
14412            if is_exasol_local_prefix {
14413                // Write LOCAL unquoted (this is special Exasol syntax, not a table reference)
14414                self.write("LOCAL");
14415            } else {
14416                self.generate_identifier(table)?;
14417            }
14418            self.write(".");
14419        }
14420        self.generate_identifier(&col.name)?;
14421        // Oracle-style join marker (+)
14422        // Only output if dialect supports it (Oracle, Exasol)
14423        if col.join_mark && self.config.supports_column_join_marks {
14424            self.write(" (+)");
14425        }
14426        // Output trailing comments
14427        for comment in &col.trailing_comments {
14428            self.write_space();
14429            self.write_formatted_comment(comment);
14430        }
14431        Ok(())
14432    }
14433
14434    /// Generate a pseudocolumn (Oracle ROWNUM, ROWID, LEVEL, etc.)
14435    /// Pseudocolumns should NEVER be quoted, as quoting breaks them in Oracle
14436    fn generate_pseudocolumn(&mut self, pc: &Pseudocolumn) -> Result<()> {
14437        use crate::dialects::DialectType;
14438        use crate::expressions::PseudocolumnType;
14439
14440        // SYSDATE -> CURRENT_TIMESTAMP for non-Oracle/Redshift dialects
14441        if pc.kind == PseudocolumnType::Sysdate
14442            && !matches!(
14443                self.config.dialect,
14444                Some(DialectType::Oracle) | Some(DialectType::Redshift) | None
14445            )
14446        {
14447            self.write_keyword("CURRENT_TIMESTAMP");
14448            // Add () for dialects that expect it
14449            if matches!(
14450                self.config.dialect,
14451                Some(DialectType::MySQL)
14452                    | Some(DialectType::ClickHouse)
14453                    | Some(DialectType::Spark)
14454                    | Some(DialectType::Databricks)
14455                    | Some(DialectType::Hive)
14456            ) {
14457                self.write("()");
14458            }
14459        } else {
14460            self.write(pc.kind.as_str());
14461        }
14462        Ok(())
14463    }
14464
14465    /// Generate CONNECT BY clause (Oracle hierarchical queries)
14466    fn generate_connect(&mut self, connect: &Connect) -> Result<()> {
14467        use crate::dialects::DialectType;
14468
14469        // Generate native CONNECT BY for Oracle and Snowflake
14470        // For other dialects, add a comment noting manual conversion needed
14471        let supports_connect_by = matches!(
14472            self.config.dialect,
14473            Some(DialectType::Oracle) | Some(DialectType::Snowflake)
14474        );
14475
14476        if !supports_connect_by && self.config.dialect.is_some() {
14477            // Add comment for unsupported dialects
14478            if self.config.pretty {
14479                self.write_newline();
14480            } else {
14481                self.write_space();
14482            }
14483            self.write("/* CONNECT BY requires manual conversion to recursive CTE */");
14484        }
14485
14486        // Generate START WITH if present (before CONNECT BY)
14487        if let Some(start) = &connect.start {
14488            if self.config.pretty {
14489                self.write_newline();
14490            } else {
14491                self.write_space();
14492            }
14493            self.write_keyword("START WITH");
14494            self.write_space();
14495            self.generate_expression(start)?;
14496        }
14497
14498        // Generate CONNECT BY
14499        if self.config.pretty {
14500            self.write_newline();
14501        } else {
14502            self.write_space();
14503        }
14504        self.write_keyword("CONNECT BY");
14505        if connect.nocycle {
14506            self.write_space();
14507            self.write_keyword("NOCYCLE");
14508        }
14509        self.write_space();
14510        self.generate_expression(&connect.connect)?;
14511
14512        Ok(())
14513    }
14514
14515    /// Generate Connect expression (for Expression::Connect variant)
14516    fn generate_connect_expr(&mut self, connect: &Connect) -> Result<()> {
14517        self.generate_connect(connect)
14518    }
14519
14520    /// Generate PRIOR expression
14521    fn generate_prior(&mut self, prior: &Prior) -> Result<()> {
14522        self.write_keyword("PRIOR");
14523        self.write_space();
14524        self.generate_expression(&prior.this)?;
14525        Ok(())
14526    }
14527
14528    /// Generate CONNECT_BY_ROOT function
14529    /// Syntax: CONNECT_BY_ROOT column (no parentheses)
14530    fn generate_connect_by_root(&mut self, cbr: &ConnectByRoot) -> Result<()> {
14531        self.write_keyword("CONNECT_BY_ROOT");
14532        self.write_space();
14533        self.generate_expression(&cbr.this)?;
14534        Ok(())
14535    }
14536
14537    /// Generate MATCH_RECOGNIZE clause
14538    fn generate_match_recognize(&mut self, mr: &MatchRecognize) -> Result<()> {
14539        use crate::dialects::DialectType;
14540
14541        // MATCH_RECOGNIZE is supported in Oracle, Snowflake, Presto, and Trino
14542        let supports_match_recognize = matches!(
14543            self.config.dialect,
14544            Some(DialectType::Oracle)
14545                | Some(DialectType::Snowflake)
14546                | Some(DialectType::Presto)
14547                | Some(DialectType::Trino)
14548        );
14549
14550        // Generate the source table first
14551        if let Some(source) = &mr.this {
14552            self.generate_expression(source)?;
14553        }
14554
14555        if !supports_match_recognize {
14556            self.write("/* MATCH_RECOGNIZE not supported in this dialect */");
14557            return Ok(());
14558        }
14559
14560        // In pretty mode, MATCH_RECOGNIZE should be on a new line
14561        if self.config.pretty {
14562            self.write_newline();
14563        } else {
14564            self.write_space();
14565        }
14566
14567        self.write_keyword("MATCH_RECOGNIZE");
14568        self.write(" (");
14569
14570        if self.config.pretty {
14571            self.indent_level += 1;
14572        }
14573
14574        let mut needs_separator = false;
14575
14576        // PARTITION BY
14577        if let Some(partition_by) = &mr.partition_by {
14578            if !partition_by.is_empty() {
14579                if self.config.pretty {
14580                    self.write_newline();
14581                    self.write_indent();
14582                }
14583                self.write_keyword("PARTITION BY");
14584                self.write_space();
14585                for (i, expr) in partition_by.iter().enumerate() {
14586                    if i > 0 {
14587                        self.write(", ");
14588                    }
14589                    self.generate_expression(expr)?;
14590                }
14591                needs_separator = true;
14592            }
14593        }
14594
14595        // ORDER BY
14596        if let Some(order_by) = &mr.order_by {
14597            if !order_by.is_empty() {
14598                if needs_separator {
14599                    if self.config.pretty {
14600                        self.write_newline();
14601                        self.write_indent();
14602                    } else {
14603                        self.write_space();
14604                    }
14605                } else if self.config.pretty {
14606                    self.write_newline();
14607                    self.write_indent();
14608                }
14609                self.write_keyword("ORDER BY");
14610                // In pretty mode, put each ORDER BY column on a new indented line
14611                if self.config.pretty {
14612                    self.indent_level += 1;
14613                    for (i, ordered) in order_by.iter().enumerate() {
14614                        if i > 0 {
14615                            self.write(",");
14616                        }
14617                        self.write_newline();
14618                        self.write_indent();
14619                        self.generate_ordered(ordered)?;
14620                    }
14621                    self.indent_level -= 1;
14622                } else {
14623                    self.write_space();
14624                    for (i, ordered) in order_by.iter().enumerate() {
14625                        if i > 0 {
14626                            self.write(", ");
14627                        }
14628                        self.generate_ordered(ordered)?;
14629                    }
14630                }
14631                needs_separator = true;
14632            }
14633        }
14634
14635        // MEASURES
14636        if let Some(measures) = &mr.measures {
14637            if !measures.is_empty() {
14638                if needs_separator {
14639                    if self.config.pretty {
14640                        self.write_newline();
14641                        self.write_indent();
14642                    } else {
14643                        self.write_space();
14644                    }
14645                } else if self.config.pretty {
14646                    self.write_newline();
14647                    self.write_indent();
14648                }
14649                self.write_keyword("MEASURES");
14650                // In pretty mode, put each MEASURE on a new indented line
14651                if self.config.pretty {
14652                    self.indent_level += 1;
14653                    for (i, measure) in measures.iter().enumerate() {
14654                        if i > 0 {
14655                            self.write(",");
14656                        }
14657                        self.write_newline();
14658                        self.write_indent();
14659                        // Handle RUNNING/FINAL prefix
14660                        if let Some(semantics) = &measure.window_frame {
14661                            match semantics {
14662                                MatchRecognizeSemantics::Running => {
14663                                    self.write_keyword("RUNNING");
14664                                    self.write_space();
14665                                }
14666                                MatchRecognizeSemantics::Final => {
14667                                    self.write_keyword("FINAL");
14668                                    self.write_space();
14669                                }
14670                            }
14671                        }
14672                        self.generate_expression(&measure.this)?;
14673                    }
14674                    self.indent_level -= 1;
14675                } else {
14676                    self.write_space();
14677                    for (i, measure) in measures.iter().enumerate() {
14678                        if i > 0 {
14679                            self.write(", ");
14680                        }
14681                        // Handle RUNNING/FINAL prefix
14682                        if let Some(semantics) = &measure.window_frame {
14683                            match semantics {
14684                                MatchRecognizeSemantics::Running => {
14685                                    self.write_keyword("RUNNING");
14686                                    self.write_space();
14687                                }
14688                                MatchRecognizeSemantics::Final => {
14689                                    self.write_keyword("FINAL");
14690                                    self.write_space();
14691                                }
14692                            }
14693                        }
14694                        self.generate_expression(&measure.this)?;
14695                    }
14696                }
14697                needs_separator = true;
14698            }
14699        }
14700
14701        // Row semantics (ONE ROW PER MATCH, ALL ROWS PER MATCH, etc.)
14702        if let Some(rows) = &mr.rows {
14703            if needs_separator {
14704                if self.config.pretty {
14705                    self.write_newline();
14706                    self.write_indent();
14707                } else {
14708                    self.write_space();
14709                }
14710            } else if self.config.pretty {
14711                self.write_newline();
14712                self.write_indent();
14713            }
14714            match rows {
14715                MatchRecognizeRows::OneRowPerMatch => {
14716                    self.write_keyword("ONE ROW PER MATCH");
14717                }
14718                MatchRecognizeRows::AllRowsPerMatch => {
14719                    self.write_keyword("ALL ROWS PER MATCH");
14720                }
14721                MatchRecognizeRows::AllRowsPerMatchShowEmptyMatches => {
14722                    self.write_keyword("ALL ROWS PER MATCH SHOW EMPTY MATCHES");
14723                }
14724                MatchRecognizeRows::AllRowsPerMatchOmitEmptyMatches => {
14725                    self.write_keyword("ALL ROWS PER MATCH OMIT EMPTY MATCHES");
14726                }
14727                MatchRecognizeRows::AllRowsPerMatchWithUnmatchedRows => {
14728                    self.write_keyword("ALL ROWS PER MATCH WITH UNMATCHED ROWS");
14729                }
14730            }
14731            needs_separator = true;
14732        }
14733
14734        // AFTER MATCH SKIP
14735        if let Some(after) = &mr.after {
14736            if needs_separator {
14737                if self.config.pretty {
14738                    self.write_newline();
14739                    self.write_indent();
14740                } else {
14741                    self.write_space();
14742                }
14743            } else if self.config.pretty {
14744                self.write_newline();
14745                self.write_indent();
14746            }
14747            match after {
14748                MatchRecognizeAfter::PastLastRow => {
14749                    self.write_keyword("AFTER MATCH SKIP PAST LAST ROW");
14750                }
14751                MatchRecognizeAfter::ToNextRow => {
14752                    self.write_keyword("AFTER MATCH SKIP TO NEXT ROW");
14753                }
14754                MatchRecognizeAfter::ToFirst(ident) => {
14755                    self.write_keyword("AFTER MATCH SKIP TO FIRST");
14756                    self.write_space();
14757                    self.generate_identifier(ident)?;
14758                }
14759                MatchRecognizeAfter::ToLast(ident) => {
14760                    self.write_keyword("AFTER MATCH SKIP TO LAST");
14761                    self.write_space();
14762                    self.generate_identifier(ident)?;
14763                }
14764            }
14765            needs_separator = true;
14766        }
14767
14768        // PATTERN
14769        if let Some(pattern) = &mr.pattern {
14770            if needs_separator {
14771                if self.config.pretty {
14772                    self.write_newline();
14773                    self.write_indent();
14774                } else {
14775                    self.write_space();
14776                }
14777            } else if self.config.pretty {
14778                self.write_newline();
14779                self.write_indent();
14780            }
14781            self.write_keyword("PATTERN");
14782            self.write_space();
14783            self.write("(");
14784            self.write(pattern);
14785            self.write(")");
14786            needs_separator = true;
14787        }
14788
14789        // DEFINE
14790        if let Some(define) = &mr.define {
14791            if !define.is_empty() {
14792                if needs_separator {
14793                    if self.config.pretty {
14794                        self.write_newline();
14795                        self.write_indent();
14796                    } else {
14797                        self.write_space();
14798                    }
14799                } else if self.config.pretty {
14800                    self.write_newline();
14801                    self.write_indent();
14802                }
14803                self.write_keyword("DEFINE");
14804                // In pretty mode, put each DEFINE on a new indented line
14805                if self.config.pretty {
14806                    self.indent_level += 1;
14807                    for (i, (name, expr)) in define.iter().enumerate() {
14808                        if i > 0 {
14809                            self.write(",");
14810                        }
14811                        self.write_newline();
14812                        self.write_indent();
14813                        self.generate_identifier(name)?;
14814                        self.write(" AS ");
14815                        self.generate_expression(expr)?;
14816                    }
14817                    self.indent_level -= 1;
14818                } else {
14819                    self.write_space();
14820                    for (i, (name, expr)) in define.iter().enumerate() {
14821                        if i > 0 {
14822                            self.write(", ");
14823                        }
14824                        self.generate_identifier(name)?;
14825                        self.write(" AS ");
14826                        self.generate_expression(expr)?;
14827                    }
14828                }
14829            }
14830        }
14831
14832        if self.config.pretty {
14833            self.indent_level -= 1;
14834            self.write_newline();
14835        }
14836        self.write(")");
14837
14838        // Alias - only include AS if it was explicitly present in the input
14839        if let Some(alias) = &mr.alias {
14840            self.write(" ");
14841            if mr.alias_explicit_as {
14842                self.write_keyword("AS");
14843                self.write(" ");
14844            }
14845            self.generate_identifier(alias)?;
14846        }
14847
14848        Ok(())
14849    }
14850
14851    /// Generate a query hint /*+ ... */
14852    fn generate_hint(&mut self, hint: &Hint) -> Result<()> {
14853        use crate::dialects::DialectType;
14854
14855        // Output hints for dialects that support them, or when no dialect is specified (identity tests)
14856        let supports_hints = matches!(
14857            self.config.dialect,
14858            None |  // No dialect = preserve everything
14859            Some(DialectType::Oracle) | Some(DialectType::MySQL) |
14860            Some(DialectType::Spark) | Some(DialectType::Hive) |
14861            Some(DialectType::Databricks) | Some(DialectType::PostgreSQL)
14862        );
14863
14864        if !supports_hints || hint.expressions.is_empty() {
14865            return Ok(());
14866        }
14867
14868        // First, expand raw hint text into individual hint strings
14869        // This handles the case where the parser stored multiple hints as a single raw string
14870        let mut hint_strings: Vec<String> = Vec::new();
14871        for expr in &hint.expressions {
14872            match expr {
14873                HintExpression::Raw(text) => {
14874                    // Parse raw hint text into individual hint function calls
14875                    let parsed = self.parse_raw_hint_text(text);
14876                    hint_strings.extend(parsed);
14877                }
14878                _ => {
14879                    hint_strings.push(self.hint_expression_to_string(expr)?);
14880                }
14881            }
14882        }
14883
14884        // In pretty mode with multiple hints, always use multiline format
14885        // This matches Python sqlglot's behavior where expressions() with default dynamic=False
14886        // always joins with newlines in pretty mode
14887        let use_multiline = self.config.pretty && hint_strings.len() > 1;
14888
14889        if use_multiline {
14890            // Pretty print with each hint on its own line
14891            self.write(" /*+ ");
14892            for (i, hint_str) in hint_strings.iter().enumerate() {
14893                if i > 0 {
14894                    self.write_newline();
14895                    self.write("  "); // 2-space indent within hint block
14896                }
14897                self.write(hint_str);
14898            }
14899            self.write(" */");
14900        } else {
14901            // Single line format
14902            self.write(" /*+ ");
14903            let sep = match self.config.dialect {
14904                Some(DialectType::Spark) | Some(DialectType::Databricks) => ", ",
14905                _ => " ",
14906            };
14907            for (i, hint_str) in hint_strings.iter().enumerate() {
14908                if i > 0 {
14909                    self.write(sep);
14910                }
14911                self.write(hint_str);
14912            }
14913            self.write(" */");
14914        }
14915
14916        Ok(())
14917    }
14918
14919    /// Parse raw hint text into individual hint function calls
14920    /// e.g., "LEADING(a b) USE_NL(c)" -> ["LEADING(a b)", "USE_NL(c)"]
14921    /// If the hint contains unparseable content (like SQL keywords), return as single raw string
14922    fn parse_raw_hint_text(&self, text: &str) -> Vec<String> {
14923        let mut results = Vec::new();
14924        let mut chars = text.chars().peekable();
14925        let mut current = String::new();
14926        let mut paren_depth = 0;
14927        let mut has_unparseable_content = false;
14928        let mut position_after_last_function = 0;
14929        let mut char_position = 0;
14930
14931        while let Some(c) = chars.next() {
14932            char_position += c.len_utf8();
14933            match c {
14934                '(' => {
14935                    paren_depth += 1;
14936                    current.push(c);
14937                }
14938                ')' => {
14939                    paren_depth -= 1;
14940                    current.push(c);
14941                    // When we close the outer parenthesis, we've completed a hint function
14942                    if paren_depth == 0 {
14943                        let trimmed = current.trim().to_string();
14944                        if !trimmed.is_empty() {
14945                            // Format this hint for pretty printing if needed
14946                            let formatted = self.format_hint_function(&trimmed);
14947                            results.push(formatted);
14948                        }
14949                        current.clear();
14950                        position_after_last_function = char_position;
14951                    }
14952                }
14953                ' ' | '\t' | '\n' | ',' if paren_depth == 0 => {
14954                    // Space/comma/whitespace outside parentheses - skip
14955                }
14956                _ if paren_depth == 0 => {
14957                    // Character outside parentheses - accumulate for potential hint name
14958                    current.push(c);
14959                }
14960                _ => {
14961                    current.push(c);
14962                }
14963            }
14964        }
14965
14966        // Check if there's remaining text after the last function call
14967        let remaining_text = text[position_after_last_function..].trim();
14968        if !remaining_text.is_empty() {
14969            // Check if it looks like valid hint function names
14970            // Valid hint identifiers typically are uppercase alphanumeric with underscores
14971            // If we see multiple words without parens, it's likely unparseable
14972            let words: Vec<&str> = remaining_text.split_whitespace().collect();
14973            let looks_like_hint_functions = words.iter().all(|word| {
14974                // A valid hint name followed by opening paren, or a standalone uppercase identifier
14975                word.contains('(') || (word.chars().all(|c| c.is_ascii_uppercase() || c == '_'))
14976            });
14977
14978            if !looks_like_hint_functions && words.len() > 1 {
14979                has_unparseable_content = true;
14980            }
14981        }
14982
14983        // If we detected unparseable content (like SQL keywords), return the whole hint as-is
14984        if has_unparseable_content {
14985            return vec![text.trim().to_string()];
14986        }
14987
14988        // If we couldn't parse anything, return the original text as a single hint
14989        if results.is_empty() {
14990            results.push(text.trim().to_string());
14991        }
14992
14993        results
14994    }
14995
14996    /// Format a hint function for pretty printing
14997    /// e.g., "LEADING(aaa bbb ccc ddd)" -> multiline if args are too wide
14998    fn format_hint_function(&self, hint: &str) -> String {
14999        if !self.config.pretty {
15000            return hint.to_string();
15001        }
15002
15003        // Try to parse NAME(args) pattern
15004        if let Some(paren_pos) = hint.find('(') {
15005            if hint.ends_with(')') {
15006                let name = &hint[..paren_pos];
15007                let args_str = &hint[paren_pos + 1..hint.len() - 1];
15008
15009                // Parse arguments (space-separated for Oracle hints)
15010                let args: Vec<&str> = args_str.split_whitespace().collect();
15011
15012                // Calculate total width of arguments
15013                let total_args_width: usize =
15014                    args.iter().map(|s| s.len()).sum::<usize>() + args.len().saturating_sub(1); // spaces between args
15015
15016                // If too wide, format on multiple lines
15017                if total_args_width > self.config.max_text_width && !args.is_empty() {
15018                    let mut result = format!("{}(\n", name);
15019                    for arg in &args {
15020                        result.push_str("    "); // 4-space indent for args
15021                        result.push_str(arg);
15022                        result.push('\n');
15023                    }
15024                    result.push_str("  )"); // 2-space indent for closing paren
15025                    return result;
15026                }
15027            }
15028        }
15029
15030        hint.to_string()
15031    }
15032
15033    /// Convert a hint expression to a string, handling multiline formatting for long arguments
15034    fn hint_expression_to_string(&mut self, expr: &HintExpression) -> Result<String> {
15035        match expr {
15036            HintExpression::Function { name, args } => {
15037                // Generate each argument to a string
15038                let arg_strings: Vec<String> = args
15039                    .iter()
15040                    .map(|arg| {
15041                        let mut gen = Generator::with_config(self.config.clone());
15042                        gen.generate_expression(arg)?;
15043                        Ok(gen.output)
15044                    })
15045                    .collect::<Result<Vec<_>>>()?;
15046
15047                // Oracle hints use space-separated arguments, not comma-separated
15048                let total_args_width: usize = arg_strings.iter().map(|s| s.len()).sum::<usize>()
15049                    + arg_strings.len().saturating_sub(1); // spaces between args
15050
15051                // Check if function args need multiline formatting
15052                // Use too_wide check for argument formatting
15053                let args_multiline =
15054                    self.config.pretty && total_args_width > self.config.max_text_width;
15055
15056                if args_multiline && !arg_strings.is_empty() {
15057                    // Multiline format for long argument lists
15058                    let mut result = format!("{}(\n", name);
15059                    for arg_str in &arg_strings {
15060                        result.push_str("    "); // 4-space indent for args
15061                        result.push_str(arg_str);
15062                        result.push('\n');
15063                    }
15064                    result.push_str("  )"); // 2-space indent for closing paren
15065                    Ok(result)
15066                } else {
15067                    // Single line format with space-separated args (Oracle style)
15068                    let args_str = arg_strings.join(" ");
15069                    Ok(format!("{}({})", name, args_str))
15070                }
15071            }
15072            HintExpression::Identifier(name) => Ok(name.clone()),
15073            HintExpression::Raw(text) => {
15074                // For pretty printing, try to format the raw text
15075                if self.config.pretty {
15076                    Ok(self.format_hint_function(text))
15077                } else {
15078                    Ok(text.clone())
15079                }
15080            }
15081        }
15082    }
15083
15084    fn generate_table(&mut self, table: &TableRef) -> Result<()> {
15085        // PostgreSQL ONLY modifier: prevents scanning child tables
15086        if table.only {
15087            self.write_keyword("ONLY");
15088            self.write_space();
15089        }
15090
15091        // Check for Snowflake IDENTIFIER() function
15092        if let Some(ref identifier_func) = table.identifier_func {
15093            self.generate_expression(identifier_func)?;
15094        } else {
15095            if let Some(catalog) = &table.catalog {
15096                self.generate_identifier(catalog)?;
15097                self.write(".");
15098            }
15099            if let Some(schema) = &table.schema {
15100                self.generate_identifier(schema)?;
15101                self.write(".");
15102            }
15103            self.generate_identifier(&table.name)?;
15104        }
15105
15106        // Output Snowflake CHANGES clause (before partition, includes its own AT/BEFORE/END)
15107        if let Some(changes) = &table.changes {
15108            self.write(" ");
15109            self.generate_changes(changes)?;
15110        }
15111
15112        // Output MySQL PARTITION clause: t1 PARTITION(p0, p1)
15113        if !table.partitions.is_empty() {
15114            self.write_space();
15115            self.write_keyword("PARTITION");
15116            self.write("(");
15117            for (i, partition) in table.partitions.iter().enumerate() {
15118                if i > 0 {
15119                    self.write(", ");
15120                }
15121                self.generate_identifier(partition)?;
15122            }
15123            self.write(")");
15124        }
15125
15126        // Output time travel clause: BEFORE (STATEMENT => ...) or AT (TIMESTAMP => ...)
15127        // Skip if CHANGES clause is present (CHANGES includes its own time travel)
15128        if table.changes.is_none() {
15129            if let Some(when) = &table.when {
15130                self.write_space();
15131                self.generate_historical_data(when)?;
15132            }
15133        }
15134
15135        // Output TSQL FOR SYSTEM_TIME temporal clause
15136        if let Some(ref system_time) = table.system_time {
15137            self.write_space();
15138            self.write(system_time);
15139        }
15140
15141        // Output Presto/Trino time travel: FOR VERSION AS OF / FOR TIMESTAMP AS OF
15142        if let Some(ref version) = table.version {
15143            self.write_space();
15144            self.generate_version(version)?;
15145        }
15146
15147        // When alias_post_tablesample is true, the order is: table TABLESAMPLE (...) alias
15148        // When alias_post_tablesample is false (default), the order is: table alias TABLESAMPLE (...)
15149        // Oracle, Hive, Spark use ALIAS_POST_TABLESAMPLE = true (alias comes after sample)
15150        let alias_post_tablesample = self.config.alias_post_tablesample;
15151
15152        if alias_post_tablesample {
15153            // TABLESAMPLE before alias (Oracle, Hive, Spark)
15154            self.generate_table_sample_clause(table)?;
15155        }
15156
15157        // Output table hints (TSQL: WITH (TABLOCK, INDEX(myindex), ...))
15158        // For SQLite, INDEXED BY hints come after the alias, so skip here
15159        let is_sqlite_hint = matches!(self.config.dialect, Some(DialectType::SQLite))
15160            && table.hints.iter().any(|h| {
15161                if let Expression::Identifier(id) = h {
15162                    id.name.starts_with("INDEXED BY") || id.name == "NOT INDEXED"
15163                } else {
15164                    false
15165                }
15166            });
15167        if !table.hints.is_empty() && !is_sqlite_hint {
15168            for hint in &table.hints {
15169                self.write_space();
15170                self.generate_expression(hint)?;
15171            }
15172        }
15173
15174        if let Some(alias) = &table.alias {
15175            self.write_space();
15176            // Output AS if it was explicitly present in the input, OR for certain dialects/cases
15177            // Generic mode and most dialects always use AS for table aliases
15178            let always_use_as = self.config.dialect.is_none()
15179                || matches!(
15180                    self.config.dialect,
15181                    Some(DialectType::Generic)
15182                        | Some(DialectType::PostgreSQL)
15183                        | Some(DialectType::Redshift)
15184                        | Some(DialectType::Snowflake)
15185                        | Some(DialectType::BigQuery)
15186                        | Some(DialectType::Presto)
15187                        | Some(DialectType::Trino)
15188                        | Some(DialectType::TSQL)
15189                        | Some(DialectType::Fabric)
15190                        | Some(DialectType::MySQL)
15191                        | Some(DialectType::Spark)
15192                        | Some(DialectType::Hive)
15193                        | Some(DialectType::SQLite)
15194                        | Some(DialectType::Drill)
15195                );
15196            let is_stage_ref = table.name.name.starts_with('@');
15197            // Oracle never uses AS for table aliases
15198            let suppress_as = matches!(self.config.dialect, Some(DialectType::Oracle));
15199            if !suppress_as && (table.alias_explicit_as || always_use_as || is_stage_ref) {
15200                self.write_keyword("AS");
15201                self.write_space();
15202            }
15203            self.generate_identifier(alias)?;
15204
15205            // Output column aliases if present: AS t(c1, c2)
15206            // Skip for dialects that don't support table alias columns (BigQuery, SQLite)
15207            if !table.column_aliases.is_empty() && self.config.supports_table_alias_columns {
15208                self.write("(");
15209                for (i, col_alias) in table.column_aliases.iter().enumerate() {
15210                    if i > 0 {
15211                        self.write(", ");
15212                    }
15213                    self.generate_identifier(col_alias)?;
15214                }
15215                self.write(")");
15216            }
15217        }
15218
15219        // For default behavior (alias_post_tablesample = false), output TABLESAMPLE after alias
15220        if !alias_post_tablesample {
15221            self.generate_table_sample_clause(table)?;
15222        }
15223
15224        // Output SQLite INDEXED BY / NOT INDEXED hints after alias
15225        if is_sqlite_hint {
15226            for hint in &table.hints {
15227                self.write_space();
15228                self.generate_expression(hint)?;
15229            }
15230        }
15231
15232        // ClickHouse FINAL modifier
15233        if table.final_ && matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
15234            self.write_space();
15235            self.write_keyword("FINAL");
15236        }
15237
15238        // Output trailing comments
15239        for comment in &table.trailing_comments {
15240            self.write_space();
15241            self.write_formatted_comment(comment);
15242        }
15243
15244        Ok(())
15245    }
15246
15247    /// Helper to output TABLESAMPLE clause for a table reference
15248    fn generate_table_sample_clause(&mut self, table: &TableRef) -> Result<()> {
15249        if let Some(ref ts) = table.table_sample {
15250            self.write_space();
15251            if ts.is_using_sample {
15252                self.write_keyword("USING SAMPLE");
15253            } else {
15254                // Use the configured tablesample keyword (e.g., "TABLESAMPLE" or "SAMPLE")
15255                self.write_keyword(self.config.tablesample_keywords);
15256            }
15257            self.generate_sample_body(ts)?;
15258            // Seed for table-level sample - use dialect's configured keyword
15259            if let Some(ref seed) = ts.seed {
15260                self.write_space();
15261                self.write_keyword(self.config.tablesample_seed_keyword);
15262                self.write(" (");
15263                self.generate_expression(seed)?;
15264                self.write(")");
15265            }
15266        }
15267        Ok(())
15268    }
15269
15270    fn generate_stage_reference(&mut self, sr: &StageReference) -> Result<()> {
15271        // Output: '@stage_name/path' if quoted, or @stage_name/path otherwise
15272        // Optionally followed by (FILE_FORMAT => 'fmt', PATTERN => '*.csv')
15273
15274        if sr.quoted {
15275            self.write("'");
15276        }
15277
15278        self.write(&sr.name);
15279        if let Some(path) = &sr.path {
15280            self.write(path);
15281        }
15282
15283        if sr.quoted {
15284            self.write("'");
15285        }
15286
15287        // Output FILE_FORMAT and PATTERN if present
15288        let has_options = sr.file_format.is_some() || sr.pattern.is_some();
15289        if has_options {
15290            self.write(" (");
15291            let mut first = true;
15292
15293            if let Some(file_format) = &sr.file_format {
15294                if !first {
15295                    self.write(", ");
15296                }
15297                self.write_keyword("FILE_FORMAT");
15298                self.write(" => ");
15299                self.generate_expression(file_format)?;
15300                first = false;
15301            }
15302
15303            if let Some(pattern) = &sr.pattern {
15304                if !first {
15305                    self.write(", ");
15306                }
15307                self.write_keyword("PATTERN");
15308                self.write(" => '");
15309                self.write(pattern);
15310                self.write("'");
15311            }
15312
15313            self.write(")");
15314        }
15315        Ok(())
15316    }
15317
15318    fn generate_star(&mut self, star: &Star) -> Result<()> {
15319        use crate::dialects::DialectType;
15320
15321        if let Some(table) = &star.table {
15322            self.generate_identifier(table)?;
15323            self.write(".");
15324        }
15325        self.write("*");
15326
15327        // Generate EXCLUDE/EXCEPT clause based on dialect
15328        if let Some(except) = &star.except {
15329            if !except.is_empty() {
15330                self.write_space();
15331                // Use dialect-appropriate keyword
15332                match self.config.dialect {
15333                    Some(DialectType::BigQuery) => self.write_keyword("EXCEPT"),
15334                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => {
15335                        self.write_keyword("EXCLUDE")
15336                    }
15337                    _ => self.write_keyword("EXCEPT"), // Default to EXCEPT
15338                }
15339                self.write(" (");
15340                for (i, col) in except.iter().enumerate() {
15341                    if i > 0 {
15342                        self.write(", ");
15343                    }
15344                    self.generate_identifier(col)?;
15345                }
15346                self.write(")");
15347            }
15348        }
15349
15350        // Generate REPLACE clause
15351        if let Some(replace) = &star.replace {
15352            if !replace.is_empty() {
15353                self.write_space();
15354                self.write_keyword("REPLACE");
15355                self.write(" (");
15356                for (i, alias) in replace.iter().enumerate() {
15357                    if i > 0 {
15358                        self.write(", ");
15359                    }
15360                    self.generate_expression(&alias.this)?;
15361                    self.write_space();
15362                    self.write_keyword("AS");
15363                    self.write_space();
15364                    self.generate_identifier(&alias.alias)?;
15365                }
15366                self.write(")");
15367            }
15368        }
15369
15370        // Generate RENAME clause (Snowflake specific)
15371        if let Some(rename) = &star.rename {
15372            if !rename.is_empty() {
15373                self.write_space();
15374                self.write_keyword("RENAME");
15375                self.write(" (");
15376                for (i, (old_name, new_name)) in rename.iter().enumerate() {
15377                    if i > 0 {
15378                        self.write(", ");
15379                    }
15380                    self.generate_identifier(old_name)?;
15381                    self.write_space();
15382                    self.write_keyword("AS");
15383                    self.write_space();
15384                    self.generate_identifier(new_name)?;
15385                }
15386                self.write(")");
15387            }
15388        }
15389
15390        // Output trailing comments
15391        for comment in &star.trailing_comments {
15392            self.write_space();
15393            self.write_formatted_comment(comment);
15394        }
15395
15396        Ok(())
15397    }
15398
15399    /// Generate Snowflake braced wildcard syntax: {*}, {tbl.*}, {* EXCLUDE (...)}, {* ILIKE '...'}
15400    fn generate_braced_wildcard(&mut self, expr: &Expression) -> Result<()> {
15401        self.write("{");
15402        match expr {
15403            Expression::Star(star) => {
15404                // Generate the star (table.* or just * with optional EXCLUDE)
15405                self.generate_star(star)?;
15406            }
15407            Expression::ILike(ilike) => {
15408                // {* ILIKE 'pattern'} syntax
15409                self.generate_expression(&ilike.left)?;
15410                self.write_space();
15411                self.write_keyword("ILIKE");
15412                self.write_space();
15413                self.generate_expression(&ilike.right)?;
15414            }
15415            _ => {
15416                self.generate_expression(expr)?;
15417            }
15418        }
15419        self.write("}");
15420        Ok(())
15421    }
15422
15423    fn generate_alias(&mut self, alias: &Alias) -> Result<()> {
15424        // Generate inner expression, but skip trailing comments if they're in pre_alias_comments
15425        // to avoid duplication (comments are captured as both Column.trailing_comments
15426        // and Alias.pre_alias_comments during parsing)
15427        match &alias.this {
15428            Expression::Column(col) => {
15429                // Generate column without trailing comments - they're in pre_alias_comments
15430                if let Some(table) = &col.table {
15431                    self.generate_identifier(table)?;
15432                    self.write(".");
15433                }
15434                self.generate_identifier(&col.name)?;
15435            }
15436            _ => {
15437                self.generate_expression(&alias.this)?;
15438            }
15439        }
15440
15441        // Handle pre-alias comments: when there are no trailing_comments, sqlglot
15442        // moves pre-alias comments to after the alias. When there are also trailing_comments,
15443        // keep pre-alias comments in their original position (between expression and AS).
15444        if !alias.pre_alias_comments.is_empty() && !alias.trailing_comments.is_empty() {
15445            for comment in &alias.pre_alias_comments {
15446                self.write_space();
15447                self.write_formatted_comment(comment);
15448            }
15449        }
15450
15451        use crate::dialects::DialectType;
15452
15453        // Determine if we should skip AS keyword for table-valued function aliases
15454        // Oracle and some other dialects don't use AS for table aliases
15455        // Note: We specifically use TableFromRows here, NOT Function, because Function
15456        // matches regular functions like MATCH_NUMBER() which should include the AS keyword.
15457        // TableFromRows represents TABLE(expr) constructs which are actual table-valued functions.
15458        let is_table_source = matches!(
15459            &alias.this,
15460            Expression::JSONTable(_)
15461                | Expression::XMLTable(_)
15462                | Expression::TableFromRows(_)
15463                | Expression::Unnest(_)
15464                | Expression::MatchRecognize(_)
15465                | Expression::Select(_)
15466                | Expression::Subquery(_)
15467                | Expression::Paren(_)
15468        );
15469        let dialect_skips_table_alias_as = matches!(self.config.dialect, Some(DialectType::Oracle));
15470        let skip_as = is_table_source && dialect_skips_table_alias_as;
15471
15472        self.write_space();
15473        if !skip_as {
15474            self.write_keyword("AS");
15475            self.write_space();
15476        }
15477
15478        // BigQuery doesn't support column aliases in table aliases: AS t(c1, c2)
15479        let skip_column_aliases = matches!(self.config.dialect, Some(DialectType::BigQuery));
15480
15481        // Check if we have column aliases only (no table alias name)
15482        if alias.alias.is_empty() && !alias.column_aliases.is_empty() && !skip_column_aliases {
15483            // Generate AS (col1, col2, ...)
15484            self.write("(");
15485            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
15486                if i > 0 {
15487                    self.write(", ");
15488                }
15489                self.generate_alias_identifier(col_alias)?;
15490            }
15491            self.write(")");
15492        } else if !alias.column_aliases.is_empty() && !skip_column_aliases {
15493            // Generate AS alias(col1, col2, ...)
15494            self.generate_alias_identifier(&alias.alias)?;
15495            self.write("(");
15496            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
15497                if i > 0 {
15498                    self.write(", ");
15499                }
15500                self.generate_alias_identifier(col_alias)?;
15501            }
15502            self.write(")");
15503        } else {
15504            // Simple alias (or BigQuery without column aliases)
15505            self.generate_alias_identifier(&alias.alias)?;
15506        }
15507
15508        // Output trailing comments (comments after the alias)
15509        for comment in &alias.trailing_comments {
15510            self.write_space();
15511            self.write_formatted_comment(comment);
15512        }
15513
15514        // Output pre-alias comments: when there are no trailing_comments, sqlglot
15515        // moves pre-alias comments to after the alias. When there are trailing_comments,
15516        // the pre-alias comments were already lost (consumed as column trailing comments
15517        // that were then used as pre_alias_comments). We always emit them after alias.
15518        if alias.trailing_comments.is_empty() {
15519            for comment in &alias.pre_alias_comments {
15520                self.write_space();
15521                self.write_formatted_comment(comment);
15522            }
15523        }
15524
15525        Ok(())
15526    }
15527
15528    fn generate_cast(&mut self, cast: &Cast) -> Result<()> {
15529        use crate::dialects::DialectType;
15530
15531        // SingleStore uses :> syntax
15532        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
15533            self.generate_expression(&cast.this)?;
15534            self.write(" :> ");
15535            self.generate_data_type(&cast.to)?;
15536            return Ok(());
15537        }
15538
15539        // Teradata: CAST(x AS FORMAT 'fmt') (no data type)
15540        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
15541            let is_unknown_type = matches!(cast.to, DataType::Unknown)
15542                || matches!(cast.to, DataType::Custom { ref name } if name.is_empty());
15543            if is_unknown_type {
15544                if let Some(format) = &cast.format {
15545                    self.write_keyword("CAST");
15546                    self.write("(");
15547                    self.generate_expression(&cast.this)?;
15548                    self.write_space();
15549                    self.write_keyword("AS");
15550                    self.write_space();
15551                    self.write_keyword("FORMAT");
15552                    self.write_space();
15553                    self.generate_expression(format)?;
15554                    self.write(")");
15555                    return Ok(());
15556                }
15557            }
15558        }
15559
15560        // Oracle: CAST(x AS DATE/TIMESTAMP ..., 'format') -> TO_DATE/TO_TIMESTAMP(x, 'format')
15561        // This follows Python sqlglot's behavior of transforming CAST with format to native functions
15562        if matches!(self.config.dialect, Some(DialectType::Oracle)) {
15563            if let Some(format) = &cast.format {
15564                // Check if target type is DATE or TIMESTAMP
15565                let is_date = matches!(cast.to, DataType::Date);
15566                let is_timestamp = matches!(cast.to, DataType::Timestamp { .. });
15567
15568                if is_date || is_timestamp {
15569                    let func_name = if is_date { "TO_DATE" } else { "TO_TIMESTAMP" };
15570                    self.write_keyword(func_name);
15571                    self.write("(");
15572                    self.generate_expression(&cast.this)?;
15573                    self.write(", ");
15574
15575                    // Normalize format string for Oracle (HH -> HH12)
15576                    // Oracle HH is 12-hour format, same as HH12. For clarity, Python sqlglot uses HH12.
15577                    if let Expression::Literal(Literal::String(fmt_str)) = format.as_ref() {
15578                        let normalized = self.normalize_oracle_format(fmt_str);
15579                        self.write("'");
15580                        self.write(&normalized);
15581                        self.write("'");
15582                    } else {
15583                        self.generate_expression(format)?;
15584                    }
15585
15586                    self.write(")");
15587                    return Ok(());
15588                }
15589            }
15590        }
15591
15592        // BigQuery: CAST(ARRAY[...] AS ARRAY<T>) -> ARRAY<T>[...]
15593        // This preserves sqlglot's typed inline array literal output.
15594        if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
15595            if let Expression::Array(arr) = &cast.this {
15596                self.generate_data_type(&cast.to)?;
15597                // Output just the bracket content [values] without the ARRAY prefix
15598                self.write("[");
15599                for (i, expr) in arr.expressions.iter().enumerate() {
15600                    if i > 0 {
15601                        self.write(", ");
15602                    }
15603                    self.generate_expression(expr)?;
15604                }
15605                self.write("]");
15606                return Ok(());
15607            }
15608            if matches!(&cast.this, Expression::ArrayFunc(_)) {
15609                self.generate_data_type(&cast.to)?;
15610                self.generate_expression(&cast.this)?;
15611                return Ok(());
15612            }
15613        }
15614
15615        // DuckDB/Presto/Trino: When CAST(Struct([unnamed]) AS STRUCT(...)),
15616        // convert the inner Struct to ROW(values...) format
15617        if matches!(
15618            self.config.dialect,
15619            Some(DialectType::DuckDB) | Some(DialectType::Presto) | Some(DialectType::Trino)
15620        ) {
15621            if let Expression::Struct(ref s) = cast.this {
15622                let all_unnamed = s.fields.iter().all(|(name, _)| name.is_none());
15623                if all_unnamed && matches!(cast.to, DataType::Struct { .. }) {
15624                    self.write_keyword("CAST");
15625                    self.write("(");
15626                    self.generate_struct_as_row(s)?;
15627                    self.write_space();
15628                    self.write_keyword("AS");
15629                    self.write_space();
15630                    self.generate_data_type(&cast.to)?;
15631                    self.write(")");
15632                    return Ok(());
15633                }
15634            }
15635        }
15636
15637        // Determine if we should use :: syntax based on dialect
15638        // PostgreSQL prefers :: for identity, most others prefer CAST()
15639        let use_double_colon = cast.double_colon_syntax && self.dialect_prefers_double_colon();
15640
15641        if use_double_colon {
15642            // PostgreSQL :: syntax: expr::type
15643            self.generate_expression(&cast.this)?;
15644            self.write("::");
15645            self.generate_data_type(&cast.to)?;
15646        } else {
15647            // Standard CAST() syntax
15648            self.write_keyword("CAST");
15649            self.write("(");
15650            self.generate_expression(&cast.this)?;
15651            self.write_space();
15652            self.write_keyword("AS");
15653            self.write_space();
15654            // For MySQL/SingleStore/TiDB, map text/blob variant types to CHAR in CAST
15655            // This matches Python sqlglot's CAST_MAPPING behavior
15656            if matches!(
15657                self.config.dialect,
15658                Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB)
15659            ) {
15660                match &cast.to {
15661                    DataType::Custom { ref name } => {
15662                        let upper = name.to_uppercase();
15663                        match upper.as_str() {
15664                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" | "LONGBLOB" | "MEDIUMBLOB"
15665                            | "TINYBLOB" => {
15666                                self.write_keyword("CHAR");
15667                            }
15668                            _ => {
15669                                self.generate_data_type(&cast.to)?;
15670                            }
15671                        }
15672                    }
15673                    DataType::VarChar { length, .. } => {
15674                        // MySQL CAST: VARCHAR -> CHAR
15675                        self.write_keyword("CHAR");
15676                        if let Some(n) = length {
15677                            self.write(&format!("({})", n));
15678                        }
15679                    }
15680                    DataType::Text => {
15681                        // MySQL CAST: TEXT -> CHAR
15682                        self.write_keyword("CHAR");
15683                    }
15684                    DataType::Timestamp {
15685                        precision,
15686                        timezone: false,
15687                    } => {
15688                        // MySQL CAST: TIMESTAMP -> DATETIME
15689                        self.write_keyword("DATETIME");
15690                        if let Some(p) = precision {
15691                            self.write(&format!("({})", p));
15692                        }
15693                    }
15694                    _ => {
15695                        self.generate_data_type(&cast.to)?;
15696                    }
15697                }
15698            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
15699                // Snowflake CAST: STRING -> VARCHAR
15700                match &cast.to {
15701                    DataType::String { length } => {
15702                        self.write_keyword("VARCHAR");
15703                        if let Some(n) = length {
15704                            self.write(&format!("({})", n));
15705                        }
15706                    }
15707                    _ => {
15708                        self.generate_data_type(&cast.to)?;
15709                    }
15710                }
15711            } else {
15712                self.generate_data_type(&cast.to)?;
15713            }
15714
15715            // Output DEFAULT ... ON CONVERSION ERROR clause if present (Oracle)
15716            if let Some(default) = &cast.default {
15717                self.write_space();
15718                self.write_keyword("DEFAULT");
15719                self.write_space();
15720                self.generate_expression(default)?;
15721                self.write_space();
15722                self.write_keyword("ON");
15723                self.write_space();
15724                self.write_keyword("CONVERSION");
15725                self.write_space();
15726                self.write_keyword("ERROR");
15727            }
15728
15729            // Output FORMAT clause if present (BigQuery: CAST(x AS STRING FORMAT 'format'))
15730            // For Oracle with comma-separated format: CAST(x AS DATE DEFAULT NULL ON CONVERSION ERROR, 'format')
15731            if let Some(format) = &cast.format {
15732                // Check if Oracle dialect - use comma syntax
15733                if matches!(
15734                    self.config.dialect,
15735                    Some(crate::dialects::DialectType::Oracle)
15736                ) {
15737                    self.write(", ");
15738                } else {
15739                    self.write_space();
15740                    self.write_keyword("FORMAT");
15741                    self.write_space();
15742                }
15743                self.generate_expression(format)?;
15744            }
15745
15746            self.write(")");
15747            // Output trailing comments
15748            for comment in &cast.trailing_comments {
15749                self.write_space();
15750                self.write_formatted_comment(comment);
15751            }
15752        }
15753        Ok(())
15754    }
15755
15756    /// Generate a Struct as ROW(values...) format, recursively converting inner Struct to ROW too.
15757    /// Used for DuckDB/Presto/Trino CAST(Struct AS STRUCT(...)) context.
15758    fn generate_struct_as_row(&mut self, s: &crate::expressions::Struct) -> Result<()> {
15759        self.write_keyword("ROW");
15760        self.write("(");
15761        for (i, (_, expr)) in s.fields.iter().enumerate() {
15762            if i > 0 {
15763                self.write(", ");
15764            }
15765            // Recursively convert inner Struct to ROW format
15766            if let Expression::Struct(ref inner_s) = expr {
15767                self.generate_struct_as_row(inner_s)?;
15768            } else {
15769                self.generate_expression(expr)?;
15770            }
15771        }
15772        self.write(")");
15773        Ok(())
15774    }
15775
15776    /// Normalize Oracle date/time format strings
15777    /// HH -> HH12 (both are 12-hour format, but Python sqlglot prefers explicit HH12)
15778    fn normalize_oracle_format(&self, format: &str) -> String {
15779        // Replace standalone HH with HH12 (but not HH12 or HH24)
15780        // We need to be careful not to replace HH12 -> HH1212 or HH24 -> HH1224
15781        let mut result = String::new();
15782        let chars: Vec<char> = format.chars().collect();
15783        let mut i = 0;
15784
15785        while i < chars.len() {
15786            if i + 1 < chars.len() && chars[i] == 'H' && chars[i + 1] == 'H' {
15787                // Check what follows HH
15788                if i + 2 < chars.len() {
15789                    let next = chars[i + 2];
15790                    if next == '1' || next == '2' {
15791                        // This is HH12 or HH24, keep as is
15792                        result.push('H');
15793                        result.push('H');
15794                        i += 2;
15795                        continue;
15796                    }
15797                }
15798                // Standalone HH -> HH12
15799                result.push_str("HH12");
15800                i += 2;
15801            } else {
15802                result.push(chars[i]);
15803                i += 1;
15804            }
15805        }
15806
15807        result
15808    }
15809
15810    /// Check if the current dialect prefers :: cast syntax
15811    /// Note: Python sqlglot normalizes all :: to CAST() for output, even for PostgreSQL
15812    /// So we return false for all dialects to match Python sqlglot's behavior
15813    fn dialect_prefers_double_colon(&self) -> bool {
15814        // Python sqlglot normalizes :: syntax to CAST() for all dialects
15815        // Even PostgreSQL outputs CAST() not ::
15816        false
15817    }
15818
15819    /// Generate MOD function - uses % operator for Snowflake/MySQL/Presto/Trino, MOD() for others
15820    fn generate_mod_func(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
15821        use crate::dialects::DialectType;
15822
15823        // Snowflake, MySQL, Presto, Trino, PostgreSQL, and DuckDB prefer x % y instead of MOD(x, y)
15824        let use_percent_operator = matches!(
15825            self.config.dialect,
15826            Some(DialectType::Snowflake)
15827                | Some(DialectType::MySQL)
15828                | Some(DialectType::Presto)
15829                | Some(DialectType::Trino)
15830                | Some(DialectType::PostgreSQL)
15831                | Some(DialectType::DuckDB)
15832                | Some(DialectType::Hive)
15833                | Some(DialectType::Spark)
15834                | Some(DialectType::Databricks)
15835                | Some(DialectType::Athena)
15836        );
15837
15838        if use_percent_operator {
15839            // Wrap complex expressions in parens to preserve precedence
15840            // Since % has higher precedence than +/-, we need parens for Add/Sub on either side
15841            let needs_paren = |e: &Expression| matches!(e, Expression::Add(_) | Expression::Sub(_));
15842            if needs_paren(&f.this) {
15843                self.write("(");
15844                self.generate_expression(&f.this)?;
15845                self.write(")");
15846            } else {
15847                self.generate_expression(&f.this)?;
15848            }
15849            self.write(" % ");
15850            if needs_paren(&f.expression) {
15851                self.write("(");
15852                self.generate_expression(&f.expression)?;
15853                self.write(")");
15854            } else {
15855                self.generate_expression(&f.expression)?;
15856            }
15857            Ok(())
15858        } else {
15859            self.generate_binary_func("MOD", &f.this, &f.expression)
15860        }
15861    }
15862
15863    /// Generate IFNULL - uses COALESCE for Snowflake, IFNULL for others
15864    fn generate_ifnull(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
15865        use crate::dialects::DialectType;
15866
15867        // Snowflake normalizes IFNULL to COALESCE
15868        let func_name = match self.config.dialect {
15869            Some(DialectType::Snowflake) => "COALESCE",
15870            _ => "IFNULL",
15871        };
15872
15873        self.generate_binary_func(func_name, &f.this, &f.expression)
15874    }
15875
15876    /// Generate NVL - preserves original name if available, otherwise uses dialect-specific output
15877    fn generate_nvl(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
15878        // Use original function name if preserved (for identity tests)
15879        if let Some(ref original_name) = f.original_name {
15880            return self.generate_binary_func(original_name, &f.this, &f.expression);
15881        }
15882
15883        // Otherwise, use dialect-specific function names
15884        use crate::dialects::DialectType;
15885        let func_name = match self.config.dialect {
15886            Some(DialectType::Snowflake)
15887            | Some(DialectType::ClickHouse)
15888            | Some(DialectType::PostgreSQL)
15889            | Some(DialectType::Presto)
15890            | Some(DialectType::Trino)
15891            | Some(DialectType::Athena)
15892            | Some(DialectType::DuckDB)
15893            | Some(DialectType::BigQuery)
15894            | Some(DialectType::Spark)
15895            | Some(DialectType::Databricks)
15896            | Some(DialectType::Hive) => "COALESCE",
15897            Some(DialectType::MySQL)
15898            | Some(DialectType::Doris)
15899            | Some(DialectType::StarRocks)
15900            | Some(DialectType::SingleStore)
15901            | Some(DialectType::TiDB) => "IFNULL",
15902            _ => "NVL",
15903        };
15904
15905        self.generate_binary_func(func_name, &f.this, &f.expression)
15906    }
15907
15908    /// Generate STDDEV_SAMP - uses STDDEV for Snowflake, STDDEV_SAMP for others
15909    fn generate_stddev_samp(&mut self, f: &crate::expressions::AggFunc) -> Result<()> {
15910        use crate::dialects::DialectType;
15911
15912        // Snowflake normalizes STDDEV_SAMP to STDDEV
15913        let func_name = match self.config.dialect {
15914            Some(DialectType::Snowflake) => "STDDEV",
15915            _ => "STDDEV_SAMP",
15916        };
15917
15918        self.generate_agg_func(func_name, f)
15919    }
15920
15921    fn generate_collation(&mut self, coll: &CollationExpr) -> Result<()> {
15922        self.generate_expression(&coll.this)?;
15923        self.write_space();
15924        self.write_keyword("COLLATE");
15925        self.write_space();
15926        if coll.quoted {
15927            // Single-quoted string: COLLATE 'de_DE'
15928            self.write("'");
15929            self.write(&coll.collation);
15930            self.write("'");
15931        } else if coll.double_quoted {
15932            // Double-quoted identifier: COLLATE "de_DE"
15933            self.write("\"");
15934            self.write(&coll.collation);
15935            self.write("\"");
15936        } else {
15937            // Unquoted identifier: COLLATE de_DE
15938            self.write(&coll.collation);
15939        }
15940        Ok(())
15941    }
15942
15943    fn generate_case(&mut self, case: &Case) -> Result<()> {
15944        // In pretty mode, decide whether to expand based on total text width
15945        let multiline_case = if self.config.pretty {
15946            // Build the flat representation to check width
15947            let mut statements: Vec<String> = Vec::new();
15948            let operand_str = if let Some(operand) = &case.operand {
15949                let s = self.generate_to_string(operand)?;
15950                statements.push(format!("CASE {}", s));
15951                s
15952            } else {
15953                statements.push("CASE".to_string());
15954                String::new()
15955            };
15956            let _ = operand_str;
15957            for (condition, result) in &case.whens {
15958                statements.push(format!("WHEN {}", self.generate_to_string(condition)?));
15959                statements.push(format!("THEN {}", self.generate_to_string(result)?));
15960            }
15961            if let Some(else_) = &case.else_ {
15962                statements.push(format!("ELSE {}", self.generate_to_string(else_)?));
15963            }
15964            statements.push("END".to_string());
15965            self.too_wide(&statements)
15966        } else {
15967            false
15968        };
15969
15970        self.write_keyword("CASE");
15971        if let Some(operand) = &case.operand {
15972            self.write_space();
15973            self.generate_expression(operand)?;
15974        }
15975        if multiline_case {
15976            self.indent_level += 1;
15977        }
15978        for (condition, result) in &case.whens {
15979            if multiline_case {
15980                self.write_newline();
15981                self.write_indent();
15982            } else {
15983                self.write_space();
15984            }
15985            self.write_keyword("WHEN");
15986            self.write_space();
15987            self.generate_expression(condition)?;
15988            if multiline_case {
15989                self.write_newline();
15990                self.write_indent();
15991            } else {
15992                self.write_space();
15993            }
15994            self.write_keyword("THEN");
15995            self.write_space();
15996            self.generate_expression(result)?;
15997        }
15998        if let Some(else_) = &case.else_ {
15999            if multiline_case {
16000                self.write_newline();
16001                self.write_indent();
16002            } else {
16003                self.write_space();
16004            }
16005            self.write_keyword("ELSE");
16006            self.write_space();
16007            self.generate_expression(else_)?;
16008        }
16009        if multiline_case {
16010            self.indent_level -= 1;
16011            self.write_newline();
16012            self.write_indent();
16013        } else {
16014            self.write_space();
16015        }
16016        self.write_keyword("END");
16017        // Emit any comments that were attached to the CASE keyword
16018        for comment in &case.comments {
16019            self.write(" ");
16020            self.write_formatted_comment(comment);
16021        }
16022        Ok(())
16023    }
16024
16025    fn generate_function(&mut self, func: &Function) -> Result<()> {
16026        // Normalize function name based on dialect settings
16027        let normalized_name = self.normalize_func_name(&func.name);
16028        let upper_name = func.name.to_uppercase();
16029
16030        // DuckDB: ARRAY_CONSTRUCT_COMPACT(a, b, c) -> LIST_FILTER([a, b, c], _u -> NOT _u IS NULL)
16031        if matches!(self.config.dialect, Some(DialectType::DuckDB))
16032            && upper_name == "ARRAY_CONSTRUCT_COMPACT"
16033        {
16034            self.write("LIST_FILTER(");
16035            self.write("[");
16036            for (i, arg) in func.args.iter().enumerate() {
16037                if i > 0 {
16038                    self.write(", ");
16039                }
16040                self.generate_expression(arg)?;
16041            }
16042            self.write("], _u -> NOT _u IS NULL)");
16043            return Ok(());
16044        }
16045
16046        // STRUCT function: BigQuery STRUCT('Alice' AS name, 85 AS score) -> dialect-specific
16047        if upper_name == "STRUCT"
16048            && !matches!(
16049                self.config.dialect,
16050                Some(DialectType::BigQuery)
16051                    | Some(DialectType::Spark)
16052                    | Some(DialectType::Databricks)
16053                    | Some(DialectType::Hive)
16054                    | None
16055            )
16056        {
16057            return self.generate_struct_function_cross_dialect(func);
16058        }
16059
16060        // SingleStore: __SS_JSON_PATH_QMARK__(expr, key) -> expr::?key
16061        // This is an internal marker function for ::? JSON path syntax
16062        if upper_name == "__SS_JSON_PATH_QMARK__" && func.args.len() == 2 {
16063            self.generate_expression(&func.args[0])?;
16064            self.write("::?");
16065            // Extract the key from the string literal
16066            if let Expression::Literal(crate::expressions::Literal::String(key)) = &func.args[1] {
16067                self.write(key);
16068            } else {
16069                self.generate_expression(&func.args[1])?;
16070            }
16071            return Ok(());
16072        }
16073
16074        // PostgreSQL: __PG_BITWISE_XOR__(a, b) -> a # b
16075        if upper_name == "__PG_BITWISE_XOR__" && func.args.len() == 2 {
16076            self.generate_expression(&func.args[0])?;
16077            self.write(" # ");
16078            self.generate_expression(&func.args[1])?;
16079            return Ok(());
16080        }
16081
16082        // Spark/Hive family: unwrap TRY(expr) since these dialects don't emit TRY as a scalar wrapper.
16083        if matches!(
16084            self.config.dialect,
16085            Some(DialectType::Spark | DialectType::Databricks | DialectType::Hive)
16086        ) && upper_name == "TRY"
16087            && func.args.len() == 1
16088        {
16089            self.generate_expression(&func.args[0])?;
16090            return Ok(());
16091        }
16092
16093        // ClickHouse normalization: toStartOfDay(x) -> dateTrunc('DAY', x)
16094        if self.config.dialect == Some(DialectType::ClickHouse)
16095            && upper_name == "TOSTARTOFDAY"
16096            && func.args.len() == 1
16097        {
16098            self.write("dateTrunc('DAY', ");
16099            self.generate_expression(&func.args[0])?;
16100            self.write(")");
16101            return Ok(());
16102        }
16103
16104        // Redshift: CONCAT(a, b, ...) -> a || b || ...
16105        if self.config.dialect == Some(DialectType::Redshift)
16106            && upper_name == "CONCAT"
16107            && func.args.len() >= 2
16108        {
16109            for (i, arg) in func.args.iter().enumerate() {
16110                if i > 0 {
16111                    self.write(" || ");
16112                }
16113                self.generate_expression(arg)?;
16114            }
16115            return Ok(());
16116        }
16117
16118        // Redshift: CONCAT_WS(delim, a, b, c) -> a || delim || b || delim || c
16119        if self.config.dialect == Some(DialectType::Redshift)
16120            && upper_name == "CONCAT_WS"
16121            && func.args.len() >= 2
16122        {
16123            let sep = &func.args[0];
16124            for (i, arg) in func.args.iter().skip(1).enumerate() {
16125                if i > 0 {
16126                    self.write(" || ");
16127                    self.generate_expression(sep)?;
16128                    self.write(" || ");
16129                }
16130                self.generate_expression(arg)?;
16131            }
16132            return Ok(());
16133        }
16134
16135        // Redshift: DATEDIFF/DATE_DIFF(unit, start, end) -> DATEDIFF(UNIT, start, end)
16136        // Unit should be unquoted uppercase identifier
16137        if self.config.dialect == Some(DialectType::Redshift)
16138            && (upper_name == "DATEDIFF" || upper_name == "DATE_DIFF")
16139            && func.args.len() == 3
16140        {
16141            self.write_keyword("DATEDIFF");
16142            self.write("(");
16143            // First arg is unit - normalize to unquoted uppercase
16144            self.write_redshift_date_part(&func.args[0]);
16145            self.write(", ");
16146            self.generate_expression(&func.args[1])?;
16147            self.write(", ");
16148            self.generate_expression(&func.args[2])?;
16149            self.write(")");
16150            return Ok(());
16151        }
16152
16153        // Redshift: DATEADD/DATE_ADD(unit, interval, date) -> DATEADD(UNIT, interval, date)
16154        // Unit should be unquoted uppercase identifier
16155        if self.config.dialect == Some(DialectType::Redshift)
16156            && (upper_name == "DATEADD" || upper_name == "DATE_ADD")
16157            && func.args.len() == 3
16158        {
16159            self.write_keyword("DATEADD");
16160            self.write("(");
16161            // First arg is unit - normalize to unquoted uppercase
16162            self.write_redshift_date_part(&func.args[0]);
16163            self.write(", ");
16164            self.generate_expression(&func.args[1])?;
16165            self.write(", ");
16166            self.generate_expression(&func.args[2])?;
16167            self.write(")");
16168            return Ok(());
16169        }
16170
16171        // UUID_STRING(args) from Snowflake -> dialect-specific UUID function (dropping args)
16172        if upper_name == "UUID_STRING"
16173            && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None)
16174        {
16175            let func_name = match self.config.dialect {
16176                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
16177                Some(DialectType::BigQuery) => "GENERATE_UUID",
16178                _ => "UUID",
16179            };
16180            self.write_keyword(func_name);
16181            self.write("()");
16182            return Ok(());
16183        }
16184
16185        // Redshift: DATE_TRUNC('unit', date) -> DATE_TRUNC('UNIT', date)
16186        // Unit should be quoted uppercase string
16187        if self.config.dialect == Some(DialectType::Redshift)
16188            && upper_name == "DATE_TRUNC"
16189            && func.args.len() == 2
16190        {
16191            self.write_keyword("DATE_TRUNC");
16192            self.write("(");
16193            // First arg is unit - normalize to quoted uppercase
16194            self.write_redshift_date_part_quoted(&func.args[0]);
16195            self.write(", ");
16196            self.generate_expression(&func.args[1])?;
16197            self.write(")");
16198            return Ok(());
16199        }
16200
16201        // TSQL/Fabric: DATE_PART -> DATEPART (no underscore)
16202        if matches!(
16203            self.config.dialect,
16204            Some(DialectType::TSQL) | Some(DialectType::Fabric)
16205        ) && (upper_name == "DATE_PART" || upper_name == "DATEPART")
16206            && func.args.len() == 2
16207        {
16208            self.write_keyword("DATEPART");
16209            self.write("(");
16210            self.generate_expression(&func.args[0])?;
16211            self.write(", ");
16212            self.generate_expression(&func.args[1])?;
16213            self.write(")");
16214            return Ok(());
16215        }
16216
16217        // PostgreSQL/Redshift: DATE_PART(part, value) -> EXTRACT(part FROM value)
16218        if matches!(
16219            self.config.dialect,
16220            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
16221        ) && (upper_name == "DATE_PART" || upper_name == "DATEPART")
16222            && func.args.len() == 2
16223        {
16224            self.write_keyword("EXTRACT");
16225            self.write("(");
16226            // Extract the datetime field - if it's a string literal, strip quotes to make it a keyword
16227            match &func.args[0] {
16228                Expression::Literal(crate::expressions::Literal::String(s)) => {
16229                    self.write(&s.to_lowercase());
16230                }
16231                _ => self.generate_expression(&func.args[0])?,
16232            }
16233            self.write_space();
16234            self.write_keyword("FROM");
16235            self.write_space();
16236            self.generate_expression(&func.args[1])?;
16237            self.write(")");
16238            return Ok(());
16239        }
16240
16241        // Dremio: DATE_PART(part, value) -> EXTRACT(part FROM value)
16242        // Also DATE literals in Dremio should be CAST(...AS DATE)
16243        if self.config.dialect == Some(DialectType::Dremio)
16244            && (upper_name == "DATE_PART" || upper_name == "DATEPART")
16245            && func.args.len() == 2
16246        {
16247            self.write_keyword("EXTRACT");
16248            self.write("(");
16249            self.generate_expression(&func.args[0])?;
16250            self.write_space();
16251            self.write_keyword("FROM");
16252            self.write_space();
16253            // For Dremio, DATE literals should become CAST('value' AS DATE)
16254            self.generate_dremio_date_expression(&func.args[1])?;
16255            self.write(")");
16256            return Ok(());
16257        }
16258
16259        // Dremio: CURRENT_DATE_UTC() -> CURRENT_DATE_UTC (no parentheses)
16260        if self.config.dialect == Some(DialectType::Dremio)
16261            && upper_name == "CURRENT_DATE_UTC"
16262            && func.args.is_empty()
16263        {
16264            self.write_keyword("CURRENT_DATE_UTC");
16265            return Ok(());
16266        }
16267
16268        // Dremio: DATETYPE(year, month, day) transformation
16269        // - If all args are integer literals: DATE('YYYY-MM-DD')
16270        // - If args are expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
16271        if self.config.dialect == Some(DialectType::Dremio)
16272            && upper_name == "DATETYPE"
16273            && func.args.len() == 3
16274        {
16275            // Helper function to extract integer from number literal
16276            fn get_int_literal(expr: &Expression) -> Option<i64> {
16277                if let Expression::Literal(crate::expressions::Literal::Number(s)) = expr {
16278                    s.parse::<i64>().ok()
16279                } else {
16280                    None
16281                }
16282            }
16283
16284            // Check if all arguments are integer literals
16285            if let (Some(year), Some(month), Some(day)) = (
16286                get_int_literal(&func.args[0]),
16287                get_int_literal(&func.args[1]),
16288                get_int_literal(&func.args[2]),
16289            ) {
16290                // All are integer literals: DATE('YYYY-MM-DD')
16291                self.write_keyword("DATE");
16292                self.write(&format!("('{:04}-{:02}-{:02}')", year, month, day));
16293                return Ok(());
16294            }
16295
16296            // For expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
16297            self.write_keyword("CAST");
16298            self.write("(");
16299            self.write_keyword("CONCAT");
16300            self.write("(");
16301            self.generate_expression(&func.args[0])?;
16302            self.write(", '-', ");
16303            self.generate_expression(&func.args[1])?;
16304            self.write(", '-', ");
16305            self.generate_expression(&func.args[2])?;
16306            self.write(")");
16307            self.write_space();
16308            self.write_keyword("AS");
16309            self.write_space();
16310            self.write_keyword("DATE");
16311            self.write(")");
16312            return Ok(());
16313        }
16314
16315        // Presto/Trino: DATE_ADD('unit', interval, date) - wrap interval in CAST(...AS BIGINT)
16316        // when it's not an integer literal
16317        let is_presto_like = matches!(
16318            self.config.dialect,
16319            Some(DialectType::Presto) | Some(DialectType::Trino)
16320        );
16321        if is_presto_like && upper_name == "DATE_ADD" && func.args.len() == 3 {
16322            self.write_keyword("DATE_ADD");
16323            self.write("(");
16324            // First arg: unit (pass through as-is, e.g., 'DAY')
16325            self.generate_expression(&func.args[0])?;
16326            self.write(", ");
16327            // Second arg: interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
16328            let interval = &func.args[1];
16329            let needs_cast = !self.returns_integer_type(interval);
16330            if needs_cast {
16331                self.write_keyword("CAST");
16332                self.write("(");
16333            }
16334            self.generate_expression(interval)?;
16335            if needs_cast {
16336                self.write_space();
16337                self.write_keyword("AS");
16338                self.write_space();
16339                self.write_keyword("BIGINT");
16340                self.write(")");
16341            }
16342            self.write(", ");
16343            // Third arg: date
16344            self.generate_expression(&func.args[2])?;
16345            self.write(")");
16346            return Ok(());
16347        }
16348
16349        // Use bracket syntax if the function was parsed with brackets (e.g., MAP[keys, values])
16350        let use_brackets = func.use_bracket_syntax;
16351
16352        // Special case: functions WITH ORDINALITY need special output order
16353        // Input: FUNC(args) WITH ORDINALITY
16354        // Stored as: name="FUNC WITH ORDINALITY", args=[...]
16355        // Output must be: FUNC(args) WITH ORDINALITY
16356        let has_ordinality = upper_name.ends_with(" WITH ORDINALITY");
16357        let output_name = if has_ordinality {
16358            let base_name = &func.name[..func.name.len() - " WITH ORDINALITY".len()];
16359            self.normalize_func_name(base_name)
16360        } else {
16361            normalized_name.clone()
16362        };
16363
16364        // For qualified names (schema.function or object.method), preserve original case
16365        // because they can be case-sensitive (e.g., TSQL XML methods like .nodes(), .value())
16366        if func.name.contains('.') && !has_ordinality {
16367            // Don't normalize qualified functions - preserve original case
16368            // If the function was quoted (e.g., BigQuery `p.d.UdF`), wrap it in backticks
16369            if func.quoted {
16370                self.write("`");
16371                self.write(&func.name);
16372                self.write("`");
16373            } else {
16374                self.write(&func.name);
16375            }
16376        } else {
16377            self.write(&output_name);
16378        }
16379
16380        // If no_parens is true and there are no args, output just the function name
16381        // Unless the target dialect requires parens for this function
16382        let force_parens = func.no_parens && func.args.is_empty() && !func.distinct && {
16383            let needs_parens = match upper_name.as_str() {
16384                "CURRENT_USER" | "SESSION_USER" | "SYSTEM_USER" => matches!(
16385                    self.config.dialect,
16386                    Some(DialectType::Snowflake)
16387                        | Some(DialectType::Spark)
16388                        | Some(DialectType::Databricks)
16389                        | Some(DialectType::Hive)
16390                ),
16391                _ => false,
16392            };
16393            !needs_parens
16394        };
16395        if force_parens {
16396            // Output trailing comments
16397            for comment in &func.trailing_comments {
16398                self.write_space();
16399                self.write_formatted_comment(comment);
16400            }
16401            return Ok(());
16402        }
16403
16404        // CUBE, ROLLUP, GROUPING SETS need a space before the parenthesis
16405        if upper_name == "CUBE" || upper_name == "ROLLUP" || upper_name == "GROUPING SETS" {
16406            self.write(" (");
16407        } else if use_brackets {
16408            self.write("[");
16409        } else {
16410            self.write("(");
16411        }
16412        if func.distinct {
16413            self.write_keyword("DISTINCT");
16414            self.write_space();
16415        }
16416
16417        // Check if arguments should be split onto multiple lines (pretty + too wide)
16418        let compact_pretty_func = matches!(self.config.dialect, Some(DialectType::Snowflake))
16419            && (upper_name == "TABLE" || upper_name == "FLATTEN");
16420        // GROUPING SETS, CUBE, ROLLUP always expand in pretty mode
16421        let is_grouping_func =
16422            upper_name == "GROUPING SETS" || upper_name == "CUBE" || upper_name == "ROLLUP";
16423        let should_split = if self.config.pretty && !func.args.is_empty() && !compact_pretty_func {
16424            if is_grouping_func {
16425                true
16426            } else {
16427                // Pre-render arguments to check total width
16428                let mut expr_strings: Vec<String> = Vec::with_capacity(func.args.len());
16429                for arg in &func.args {
16430                    let mut temp_gen = Generator::with_config(self.config.clone());
16431                    temp_gen.config.pretty = false; // Don't recurse into pretty
16432                    temp_gen.generate_expression(arg)?;
16433                    expr_strings.push(temp_gen.output);
16434                }
16435                self.too_wide(&expr_strings)
16436            }
16437        } else {
16438            false
16439        };
16440
16441        if should_split {
16442            // Split onto multiple lines
16443            self.write_newline();
16444            self.indent_level += 1;
16445            for (i, arg) in func.args.iter().enumerate() {
16446                self.write_indent();
16447                self.generate_expression(arg)?;
16448                if i + 1 < func.args.len() {
16449                    self.write(",");
16450                }
16451                self.write_newline();
16452            }
16453            self.indent_level -= 1;
16454            self.write_indent();
16455        } else {
16456            // All on one line
16457            for (i, arg) in func.args.iter().enumerate() {
16458                if i > 0 {
16459                    self.write(", ");
16460                }
16461                self.generate_expression(arg)?;
16462            }
16463        }
16464
16465        if use_brackets {
16466            self.write("]");
16467        } else {
16468            self.write(")");
16469        }
16470        // Append WITH ORDINALITY after closing paren for table-valued functions
16471        if has_ordinality {
16472            self.write_space();
16473            self.write_keyword("WITH ORDINALITY");
16474        }
16475        // Output trailing comments
16476        for comment in &func.trailing_comments {
16477            self.write_space();
16478            self.write_formatted_comment(comment);
16479        }
16480        Ok(())
16481    }
16482
16483    fn generate_aggregate_function(&mut self, func: &AggregateFunction) -> Result<()> {
16484        // Normalize function name based on dialect settings
16485        let mut normalized_name = self.normalize_func_name(&func.name);
16486
16487        // Dialect-specific name mappings for aggregate functions
16488        let upper = normalized_name.to_uppercase();
16489        if upper == "MAX_BY" || upper == "MIN_BY" {
16490            let is_max = upper == "MAX_BY";
16491            match self.config.dialect {
16492                Some(DialectType::ClickHouse) => {
16493                    normalized_name = if is_max {
16494                        "argMax".to_string()
16495                    } else {
16496                        "argMin".to_string()
16497                    };
16498                }
16499                Some(DialectType::DuckDB) => {
16500                    normalized_name = if is_max {
16501                        "ARG_MAX".to_string()
16502                    } else {
16503                        "ARG_MIN".to_string()
16504                    };
16505                }
16506                _ => {}
16507            }
16508        }
16509        self.write(&normalized_name);
16510        self.write("(");
16511        if func.distinct {
16512            self.write_keyword("DISTINCT");
16513            self.write_space();
16514        }
16515
16516        // Check if we need to transform multi-arg COUNT DISTINCT
16517        // When dialect doesn't support multi_arg_distinct, transform:
16518        // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
16519        let is_count = normalized_name.eq_ignore_ascii_case("COUNT");
16520        let needs_multi_arg_transform =
16521            func.distinct && is_count && func.args.len() > 1 && !self.config.multi_arg_distinct;
16522
16523        if needs_multi_arg_transform {
16524            // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
16525            self.write_keyword("CASE");
16526            for arg in &func.args {
16527                self.write_space();
16528                self.write_keyword("WHEN");
16529                self.write_space();
16530                self.generate_expression(arg)?;
16531                self.write_space();
16532                self.write_keyword("IS NULL THEN NULL");
16533            }
16534            self.write_space();
16535            self.write_keyword("ELSE");
16536            self.write(" (");
16537            for (i, arg) in func.args.iter().enumerate() {
16538                if i > 0 {
16539                    self.write(", ");
16540                }
16541                self.generate_expression(arg)?;
16542            }
16543            self.write(")");
16544            self.write_space();
16545            self.write_keyword("END");
16546        } else {
16547            for (i, arg) in func.args.iter().enumerate() {
16548                if i > 0 {
16549                    self.write(", ");
16550                }
16551                self.generate_expression(arg)?;
16552            }
16553        }
16554
16555        // IGNORE NULLS / RESPECT NULLS inside parens (for BigQuery style or when config says in_func)
16556        if self.config.ignore_nulls_in_func
16557            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
16558        {
16559            if let Some(ignore) = func.ignore_nulls {
16560                self.write_space();
16561                if ignore {
16562                    self.write_keyword("IGNORE NULLS");
16563                } else {
16564                    self.write_keyword("RESPECT NULLS");
16565                }
16566            }
16567        }
16568
16569        // ORDER BY inside aggregate
16570        if !func.order_by.is_empty() {
16571            self.write_space();
16572            self.write_keyword("ORDER BY");
16573            self.write_space();
16574            for (i, ord) in func.order_by.iter().enumerate() {
16575                if i > 0 {
16576                    self.write(", ");
16577                }
16578                self.generate_ordered(ord)?;
16579            }
16580        }
16581
16582        // LIMIT inside aggregate
16583        if let Some(limit) = &func.limit {
16584            self.write_space();
16585            self.write_keyword("LIMIT");
16586            self.write_space();
16587            // Check if this is a Tuple representing LIMIT offset, count
16588            if let Expression::Tuple(t) = limit.as_ref() {
16589                if t.expressions.len() == 2 {
16590                    self.generate_expression(&t.expressions[0])?;
16591                    self.write(", ");
16592                    self.generate_expression(&t.expressions[1])?;
16593                } else {
16594                    self.generate_expression(limit)?;
16595                }
16596            } else {
16597                self.generate_expression(limit)?;
16598            }
16599        }
16600
16601        self.write(")");
16602
16603        // IGNORE NULLS / RESPECT NULLS outside parens (standard style)
16604        if !self.config.ignore_nulls_in_func
16605            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
16606        {
16607            if let Some(ignore) = func.ignore_nulls {
16608                self.write_space();
16609                if ignore {
16610                    self.write_keyword("IGNORE NULLS");
16611                } else {
16612                    self.write_keyword("RESPECT NULLS");
16613                }
16614            }
16615        }
16616
16617        if let Some(filter) = &func.filter {
16618            self.write_space();
16619            self.write_keyword("FILTER");
16620            self.write("(");
16621            self.write_keyword("WHERE");
16622            self.write_space();
16623            self.generate_expression(filter)?;
16624            self.write(")");
16625        }
16626
16627        Ok(())
16628    }
16629
16630    fn generate_window_function(&mut self, wf: &WindowFunction) -> Result<()> {
16631        self.generate_expression(&wf.this)?;
16632
16633        // Generate KEEP clause if present (Oracle KEEP (DENSE_RANK FIRST|LAST ORDER BY ...))
16634        if let Some(keep) = &wf.keep {
16635            self.write_space();
16636            self.write_keyword("KEEP");
16637            self.write(" (");
16638            self.write_keyword("DENSE_RANK");
16639            self.write_space();
16640            if keep.first {
16641                self.write_keyword("FIRST");
16642            } else {
16643                self.write_keyword("LAST");
16644            }
16645            self.write_space();
16646            self.write_keyword("ORDER BY");
16647            self.write_space();
16648            for (i, ord) in keep.order_by.iter().enumerate() {
16649                if i > 0 {
16650                    self.write(", ");
16651                }
16652                self.generate_ordered(ord)?;
16653            }
16654            self.write(")");
16655        }
16656
16657        // Check if there's any OVER clause content
16658        let has_over = !wf.over.partition_by.is_empty()
16659            || !wf.over.order_by.is_empty()
16660            || wf.over.frame.is_some()
16661            || wf.over.window_name.is_some();
16662
16663        // Only output OVER if there's actual window specification (not just KEEP alone)
16664        if has_over {
16665            self.write_space();
16666            self.write_keyword("OVER");
16667
16668            // Check if this is just a bare named window reference (no parens needed)
16669            let has_specs = !wf.over.partition_by.is_empty()
16670                || !wf.over.order_by.is_empty()
16671                || wf.over.frame.is_some();
16672
16673            if wf.over.window_name.is_some() && !has_specs {
16674                // OVER window_name (without parentheses)
16675                self.write_space();
16676                self.write(&wf.over.window_name.as_ref().unwrap().name);
16677            } else {
16678                // OVER (...) or OVER (window_name ...)
16679                self.write(" (");
16680                self.generate_over(&wf.over)?;
16681                self.write(")");
16682            }
16683        } else if wf.keep.is_none() {
16684            // No KEEP and no OVER content, but still a WindowFunction - output empty OVER ()
16685            self.write_space();
16686            self.write_keyword("OVER");
16687            self.write(" ()");
16688        }
16689
16690        Ok(())
16691    }
16692
16693    /// Generate WITHIN GROUP clause (for ordered-set aggregate functions)
16694    fn generate_within_group(&mut self, wg: &WithinGroup) -> Result<()> {
16695        self.generate_expression(&wg.this)?;
16696        self.write_space();
16697        self.write_keyword("WITHIN GROUP");
16698        self.write(" (");
16699        self.write_keyword("ORDER BY");
16700        self.write_space();
16701        for (i, ord) in wg.order_by.iter().enumerate() {
16702            if i > 0 {
16703                self.write(", ");
16704            }
16705            self.generate_ordered(ord)?;
16706        }
16707        self.write(")");
16708        Ok(())
16709    }
16710
16711    /// Generate the contents of an OVER clause (without parentheses)
16712    fn generate_over(&mut self, over: &Over) -> Result<()> {
16713        let mut has_content = false;
16714
16715        // Named window reference
16716        if let Some(name) = &over.window_name {
16717            self.write(&name.name);
16718            has_content = true;
16719        }
16720
16721        // PARTITION BY
16722        if !over.partition_by.is_empty() {
16723            if has_content {
16724                self.write_space();
16725            }
16726            self.write_keyword("PARTITION BY");
16727            self.write_space();
16728            for (i, expr) in over.partition_by.iter().enumerate() {
16729                if i > 0 {
16730                    self.write(", ");
16731                }
16732                self.generate_expression(expr)?;
16733            }
16734            has_content = true;
16735        }
16736
16737        // ORDER BY
16738        if !over.order_by.is_empty() {
16739            if has_content {
16740                self.write_space();
16741            }
16742            self.write_keyword("ORDER BY");
16743            self.write_space();
16744            for (i, ordered) in over.order_by.iter().enumerate() {
16745                if i > 0 {
16746                    self.write(", ");
16747                }
16748                self.generate_ordered(ordered)?;
16749            }
16750            has_content = true;
16751        }
16752
16753        // Window frame
16754        if let Some(frame) = &over.frame {
16755            if has_content {
16756                self.write_space();
16757            }
16758            self.generate_window_frame(frame)?;
16759        }
16760
16761        Ok(())
16762    }
16763
16764    fn generate_window_frame(&mut self, frame: &WindowFrame) -> Result<()> {
16765        // Exasol uses lowercase for frame kind (rows/range/groups)
16766        let lowercase_frame = self.config.lowercase_window_frame_keywords;
16767
16768        // Use preserved kind_text if available (for case preservation), unless lowercase override is active
16769        if !lowercase_frame {
16770            if let Some(kind_text) = &frame.kind_text {
16771                self.write(kind_text);
16772            } else {
16773                match frame.kind {
16774                    WindowFrameKind::Rows => self.write_keyword("ROWS"),
16775                    WindowFrameKind::Range => self.write_keyword("RANGE"),
16776                    WindowFrameKind::Groups => self.write_keyword("GROUPS"),
16777                }
16778            }
16779        } else {
16780            match frame.kind {
16781                WindowFrameKind::Rows => self.write("rows"),
16782                WindowFrameKind::Range => self.write("range"),
16783                WindowFrameKind::Groups => self.write("groups"),
16784            }
16785        }
16786
16787        // Use BETWEEN format only when there's an explicit end bound,
16788        // or when normalize_window_frame_between is enabled and the start is a directional bound
16789        self.write_space();
16790        let should_normalize = self.config.normalize_window_frame_between
16791            && frame.end.is_none()
16792            && matches!(
16793                frame.start,
16794                WindowFrameBound::Preceding(_)
16795                    | WindowFrameBound::Following(_)
16796                    | WindowFrameBound::UnboundedPreceding
16797                    | WindowFrameBound::UnboundedFollowing
16798            );
16799
16800        if let Some(end) = &frame.end {
16801            // BETWEEN format: RANGE BETWEEN start AND end
16802            self.write_keyword("BETWEEN");
16803            self.write_space();
16804            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
16805            self.write_space();
16806            self.write_keyword("AND");
16807            self.write_space();
16808            self.generate_window_frame_bound(end, frame.end_side_text.as_deref())?;
16809        } else if should_normalize {
16810            // Normalize single-bound to BETWEEN form: ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
16811            self.write_keyword("BETWEEN");
16812            self.write_space();
16813            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
16814            self.write_space();
16815            self.write_keyword("AND");
16816            self.write_space();
16817            self.write_keyword("CURRENT ROW");
16818        } else {
16819            // Single bound format: RANGE CURRENT ROW
16820            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
16821        }
16822
16823        // EXCLUDE clause
16824        if let Some(exclude) = &frame.exclude {
16825            self.write_space();
16826            self.write_keyword("EXCLUDE");
16827            self.write_space();
16828            match exclude {
16829                WindowFrameExclude::CurrentRow => self.write_keyword("CURRENT ROW"),
16830                WindowFrameExclude::Group => self.write_keyword("GROUP"),
16831                WindowFrameExclude::Ties => self.write_keyword("TIES"),
16832                WindowFrameExclude::NoOthers => self.write_keyword("NO OTHERS"),
16833            }
16834        }
16835
16836        Ok(())
16837    }
16838
16839    fn generate_window_frame_bound(
16840        &mut self,
16841        bound: &WindowFrameBound,
16842        side_text: Option<&str>,
16843    ) -> Result<()> {
16844        // Exasol uses lowercase for preceding/following
16845        let lowercase_frame = self.config.lowercase_window_frame_keywords;
16846
16847        match bound {
16848            WindowFrameBound::CurrentRow => {
16849                self.write_keyword("CURRENT ROW");
16850            }
16851            WindowFrameBound::UnboundedPreceding => {
16852                self.write_keyword("UNBOUNDED");
16853                self.write_space();
16854                if lowercase_frame {
16855                    self.write("preceding");
16856                } else if let Some(text) = side_text {
16857                    self.write(text);
16858                } else {
16859                    self.write_keyword("PRECEDING");
16860                }
16861            }
16862            WindowFrameBound::UnboundedFollowing => {
16863                self.write_keyword("UNBOUNDED");
16864                self.write_space();
16865                if lowercase_frame {
16866                    self.write("following");
16867                } else if let Some(text) = side_text {
16868                    self.write(text);
16869                } else {
16870                    self.write_keyword("FOLLOWING");
16871                }
16872            }
16873            WindowFrameBound::Preceding(expr) => {
16874                self.generate_expression(expr)?;
16875                self.write_space();
16876                if lowercase_frame {
16877                    self.write("preceding");
16878                } else if let Some(text) = side_text {
16879                    self.write(text);
16880                } else {
16881                    self.write_keyword("PRECEDING");
16882                }
16883            }
16884            WindowFrameBound::Following(expr) => {
16885                self.generate_expression(expr)?;
16886                self.write_space();
16887                if lowercase_frame {
16888                    self.write("following");
16889                } else if let Some(text) = side_text {
16890                    self.write(text);
16891                } else {
16892                    self.write_keyword("FOLLOWING");
16893                }
16894            }
16895            WindowFrameBound::BarePreceding => {
16896                if lowercase_frame {
16897                    self.write("preceding");
16898                } else if let Some(text) = side_text {
16899                    self.write(text);
16900                } else {
16901                    self.write_keyword("PRECEDING");
16902                }
16903            }
16904            WindowFrameBound::BareFollowing => {
16905                if lowercase_frame {
16906                    self.write("following");
16907                } else if let Some(text) = side_text {
16908                    self.write(text);
16909                } else {
16910                    self.write_keyword("FOLLOWING");
16911                }
16912            }
16913            WindowFrameBound::Value(expr) => {
16914                // Bare numeric bound without PRECEDING/FOLLOWING
16915                self.generate_expression(expr)?;
16916            }
16917        }
16918        Ok(())
16919    }
16920
16921    fn generate_interval(&mut self, interval: &Interval) -> Result<()> {
16922        // For Oracle with ExprSpan: only output INTERVAL if `this` is a literal
16923        // (e.g., `(expr) DAY(9) TO SECOND(3)` should NOT have INTERVAL prefix)
16924        let skip_interval_keyword = matches!(self.config.dialect, Some(DialectType::Oracle))
16925            && matches!(&interval.unit, Some(IntervalUnitSpec::ExprSpan(_)))
16926            && !matches!(&interval.this, Some(Expression::Literal(_)));
16927
16928        // SINGLE_STRING_INTERVAL: combine value and unit into a single quoted string
16929        // e.g., INTERVAL '1' DAY -> INTERVAL '1 DAY'
16930        if self.config.single_string_interval {
16931            if let (
16932                Some(Expression::Literal(Literal::String(ref val))),
16933                Some(IntervalUnitSpec::Simple {
16934                    ref unit,
16935                    ref use_plural,
16936                }),
16937            ) = (&interval.this, &interval.unit)
16938            {
16939                self.write_keyword("INTERVAL");
16940                self.write_space();
16941                let effective_plural = *use_plural && self.config.interval_allows_plural_form;
16942                let unit_str = self.interval_unit_str(unit, effective_plural);
16943                self.write("'");
16944                self.write(val);
16945                self.write(" ");
16946                self.write(&unit_str);
16947                self.write("'");
16948                return Ok(());
16949            }
16950        }
16951
16952        if !skip_interval_keyword {
16953            self.write_keyword("INTERVAL");
16954        }
16955
16956        // Generate value if present
16957        if let Some(ref value) = interval.this {
16958            if !skip_interval_keyword {
16959                self.write_space();
16960            }
16961            // If the value is a complex expression (not a literal/column/function call)
16962            // and there's a unit, wrap it in parentheses
16963            // e.g., INTERVAL (2 * 2) MONTH, INTERVAL (DAYOFMONTH(dt) - 1) DAY
16964            let needs_parens = interval.unit.is_some()
16965                && matches!(
16966                    value,
16967                    Expression::Add(_)
16968                        | Expression::Sub(_)
16969                        | Expression::Mul(_)
16970                        | Expression::Div(_)
16971                        | Expression::Mod(_)
16972                        | Expression::BitwiseAnd(_)
16973                        | Expression::BitwiseOr(_)
16974                        | Expression::BitwiseXor(_)
16975                );
16976            if needs_parens {
16977                self.write("(");
16978            }
16979            self.generate_expression(value)?;
16980            if needs_parens {
16981                self.write(")");
16982            }
16983        }
16984
16985        // Generate unit if present
16986        if let Some(ref unit_spec) = interval.unit {
16987            self.write_space();
16988            self.write_interval_unit_spec(unit_spec)?;
16989        }
16990
16991        Ok(())
16992    }
16993
16994    /// Return the string representation of an interval unit
16995    fn interval_unit_str(&self, unit: &IntervalUnit, use_plural: bool) -> &'static str {
16996        match (unit, use_plural) {
16997            (IntervalUnit::Year, false) => "YEAR",
16998            (IntervalUnit::Year, true) => "YEARS",
16999            (IntervalUnit::Quarter, false) => "QUARTER",
17000            (IntervalUnit::Quarter, true) => "QUARTERS",
17001            (IntervalUnit::Month, false) => "MONTH",
17002            (IntervalUnit::Month, true) => "MONTHS",
17003            (IntervalUnit::Week, false) => "WEEK",
17004            (IntervalUnit::Week, true) => "WEEKS",
17005            (IntervalUnit::Day, false) => "DAY",
17006            (IntervalUnit::Day, true) => "DAYS",
17007            (IntervalUnit::Hour, false) => "HOUR",
17008            (IntervalUnit::Hour, true) => "HOURS",
17009            (IntervalUnit::Minute, false) => "MINUTE",
17010            (IntervalUnit::Minute, true) => "MINUTES",
17011            (IntervalUnit::Second, false) => "SECOND",
17012            (IntervalUnit::Second, true) => "SECONDS",
17013            (IntervalUnit::Millisecond, false) => "MILLISECOND",
17014            (IntervalUnit::Millisecond, true) => "MILLISECONDS",
17015            (IntervalUnit::Microsecond, false) => "MICROSECOND",
17016            (IntervalUnit::Microsecond, true) => "MICROSECONDS",
17017            (IntervalUnit::Nanosecond, false) => "NANOSECOND",
17018            (IntervalUnit::Nanosecond, true) => "NANOSECONDS",
17019        }
17020    }
17021
17022    fn write_interval_unit_spec(&mut self, unit_spec: &IntervalUnitSpec) -> Result<()> {
17023        match unit_spec {
17024            IntervalUnitSpec::Simple { unit, use_plural } => {
17025                // If dialect doesn't allow plural forms, force singular
17026                let effective_plural = *use_plural && self.config.interval_allows_plural_form;
17027                self.write_simple_interval_unit(unit, effective_plural);
17028            }
17029            IntervalUnitSpec::Span(span) => {
17030                self.write_simple_interval_unit(&span.this, false);
17031                self.write_space();
17032                self.write_keyword("TO");
17033                self.write_space();
17034                self.write_simple_interval_unit(&span.expression, false);
17035            }
17036            IntervalUnitSpec::ExprSpan(span) => {
17037                // Expression-based interval span (e.g., DAY(9) TO SECOND(3))
17038                self.generate_expression(&span.this)?;
17039                self.write_space();
17040                self.write_keyword("TO");
17041                self.write_space();
17042                self.generate_expression(&span.expression)?;
17043            }
17044            IntervalUnitSpec::Expr(expr) => {
17045                self.generate_expression(expr)?;
17046            }
17047        }
17048        Ok(())
17049    }
17050
17051    fn write_simple_interval_unit(&mut self, unit: &IntervalUnit, use_plural: bool) {
17052        // Output interval unit, respecting plural preference
17053        match (unit, use_plural) {
17054            (IntervalUnit::Year, false) => self.write_keyword("YEAR"),
17055            (IntervalUnit::Year, true) => self.write_keyword("YEARS"),
17056            (IntervalUnit::Quarter, false) => self.write_keyword("QUARTER"),
17057            (IntervalUnit::Quarter, true) => self.write_keyword("QUARTERS"),
17058            (IntervalUnit::Month, false) => self.write_keyword("MONTH"),
17059            (IntervalUnit::Month, true) => self.write_keyword("MONTHS"),
17060            (IntervalUnit::Week, false) => self.write_keyword("WEEK"),
17061            (IntervalUnit::Week, true) => self.write_keyword("WEEKS"),
17062            (IntervalUnit::Day, false) => self.write_keyword("DAY"),
17063            (IntervalUnit::Day, true) => self.write_keyword("DAYS"),
17064            (IntervalUnit::Hour, false) => self.write_keyword("HOUR"),
17065            (IntervalUnit::Hour, true) => self.write_keyword("HOURS"),
17066            (IntervalUnit::Minute, false) => self.write_keyword("MINUTE"),
17067            (IntervalUnit::Minute, true) => self.write_keyword("MINUTES"),
17068            (IntervalUnit::Second, false) => self.write_keyword("SECOND"),
17069            (IntervalUnit::Second, true) => self.write_keyword("SECONDS"),
17070            (IntervalUnit::Millisecond, false) => self.write_keyword("MILLISECOND"),
17071            (IntervalUnit::Millisecond, true) => self.write_keyword("MILLISECONDS"),
17072            (IntervalUnit::Microsecond, false) => self.write_keyword("MICROSECOND"),
17073            (IntervalUnit::Microsecond, true) => self.write_keyword("MICROSECONDS"),
17074            (IntervalUnit::Nanosecond, false) => self.write_keyword("NANOSECOND"),
17075            (IntervalUnit::Nanosecond, true) => self.write_keyword("NANOSECONDS"),
17076        }
17077    }
17078
17079    /// Normalize a date part expression to unquoted uppercase for Redshift DATEDIFF/DATEADD
17080    /// Converts: 'day', 'days', day, days, DAY -> DAY (unquoted)
17081    fn write_redshift_date_part(&mut self, expr: &Expression) {
17082        let part_str = self.extract_date_part_string(expr);
17083        if let Some(part) = part_str {
17084            let normalized = self.normalize_date_part(&part);
17085            self.write_keyword(&normalized);
17086        } else {
17087            // If we can't extract a date part string, fall back to generating the expression
17088            let _ = self.generate_expression(expr);
17089        }
17090    }
17091
17092    /// Normalize a date part expression to quoted uppercase for Redshift DATE_TRUNC
17093    /// Converts: 'day', day, DAY -> 'DAY' (quoted)
17094    fn write_redshift_date_part_quoted(&mut self, expr: &Expression) {
17095        let part_str = self.extract_date_part_string(expr);
17096        if let Some(part) = part_str {
17097            let normalized = self.normalize_date_part(&part);
17098            self.write("'");
17099            self.write(&normalized);
17100            self.write("'");
17101        } else {
17102            // If we can't extract a date part string, fall back to generating the expression
17103            let _ = self.generate_expression(expr);
17104        }
17105    }
17106
17107    /// Extract date part string from expression (handles string literals and identifiers)
17108    fn extract_date_part_string(&self, expr: &Expression) -> Option<String> {
17109        match expr {
17110            Expression::Literal(crate::expressions::Literal::String(s)) => Some(s.clone()),
17111            Expression::Identifier(id) => Some(id.name.clone()),
17112            Expression::Column(col) if col.table.is_none() => {
17113                // Simple column reference without table prefix, treat as identifier
17114                Some(col.name.name.clone())
17115            }
17116            _ => None,
17117        }
17118    }
17119
17120    /// Normalize date part to uppercase singular form
17121    /// days -> DAY, months -> MONTH, etc.
17122    fn normalize_date_part(&self, part: &str) -> String {
17123        let lower = part.to_lowercase();
17124        match lower.as_str() {
17125            "day" | "days" | "d" => "DAY".to_string(),
17126            "month" | "months" | "mon" | "mm" => "MONTH".to_string(),
17127            "year" | "years" | "y" | "yy" | "yyyy" => "YEAR".to_string(),
17128            "week" | "weeks" | "w" | "wk" => "WEEK".to_string(),
17129            "hour" | "hours" | "h" | "hh" => "HOUR".to_string(),
17130            "minute" | "minutes" | "m" | "mi" | "n" => "MINUTE".to_string(),
17131            "second" | "seconds" | "s" | "ss" => "SECOND".to_string(),
17132            "millisecond" | "milliseconds" | "ms" => "MILLISECOND".to_string(),
17133            "microsecond" | "microseconds" | "us" => "MICROSECOND".to_string(),
17134            "quarter" | "quarters" | "q" | "qq" => "QUARTER".to_string(),
17135            _ => part.to_uppercase(),
17136        }
17137    }
17138
17139    fn write_datetime_field(&mut self, field: &DateTimeField) {
17140        match field {
17141            DateTimeField::Year => self.write_keyword("YEAR"),
17142            DateTimeField::Month => self.write_keyword("MONTH"),
17143            DateTimeField::Day => self.write_keyword("DAY"),
17144            DateTimeField::Hour => self.write_keyword("HOUR"),
17145            DateTimeField::Minute => self.write_keyword("MINUTE"),
17146            DateTimeField::Second => self.write_keyword("SECOND"),
17147            DateTimeField::Millisecond => self.write_keyword("MILLISECOND"),
17148            DateTimeField::Microsecond => self.write_keyword("MICROSECOND"),
17149            DateTimeField::DayOfWeek => {
17150                let name = match self.config.dialect {
17151                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFWEEK",
17152                    _ => "DOW",
17153                };
17154                self.write_keyword(name);
17155            }
17156            DateTimeField::DayOfYear => {
17157                let name = match self.config.dialect {
17158                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFYEAR",
17159                    _ => "DOY",
17160                };
17161                self.write_keyword(name);
17162            }
17163            DateTimeField::Week => self.write_keyword("WEEK"),
17164            DateTimeField::WeekWithModifier(modifier) => {
17165                self.write_keyword("WEEK");
17166                self.write("(");
17167                self.write(modifier);
17168                self.write(")");
17169            }
17170            DateTimeField::Quarter => self.write_keyword("QUARTER"),
17171            DateTimeField::Epoch => self.write_keyword("EPOCH"),
17172            DateTimeField::Timezone => self.write_keyword("TIMEZONE"),
17173            DateTimeField::TimezoneHour => self.write_keyword("TIMEZONE_HOUR"),
17174            DateTimeField::TimezoneMinute => self.write_keyword("TIMEZONE_MINUTE"),
17175            DateTimeField::Date => self.write_keyword("DATE"),
17176            DateTimeField::Time => self.write_keyword("TIME"),
17177            DateTimeField::Custom(name) => self.write(name),
17178        }
17179    }
17180
17181    /// Write datetime field in lowercase (for Spark/Hive/Databricks)
17182    fn write_datetime_field_lower(&mut self, field: &DateTimeField) {
17183        match field {
17184            DateTimeField::Year => self.write("year"),
17185            DateTimeField::Month => self.write("month"),
17186            DateTimeField::Day => self.write("day"),
17187            DateTimeField::Hour => self.write("hour"),
17188            DateTimeField::Minute => self.write("minute"),
17189            DateTimeField::Second => self.write("second"),
17190            DateTimeField::Millisecond => self.write("millisecond"),
17191            DateTimeField::Microsecond => self.write("microsecond"),
17192            DateTimeField::DayOfWeek => self.write("dow"),
17193            DateTimeField::DayOfYear => self.write("doy"),
17194            DateTimeField::Week => self.write("week"),
17195            DateTimeField::WeekWithModifier(modifier) => {
17196                self.write("week(");
17197                self.write(modifier);
17198                self.write(")");
17199            }
17200            DateTimeField::Quarter => self.write("quarter"),
17201            DateTimeField::Epoch => self.write("epoch"),
17202            DateTimeField::Timezone => self.write("timezone"),
17203            DateTimeField::TimezoneHour => self.write("timezone_hour"),
17204            DateTimeField::TimezoneMinute => self.write("timezone_minute"),
17205            DateTimeField::Date => self.write("date"),
17206            DateTimeField::Time => self.write("time"),
17207            DateTimeField::Custom(name) => self.write(name),
17208        }
17209    }
17210
17211    // Helper function generators
17212
17213    fn generate_simple_func(&mut self, name: &str, arg: &Expression) -> Result<()> {
17214        self.write_keyword(name);
17215        self.write("(");
17216        self.generate_expression(arg)?;
17217        self.write(")");
17218        Ok(())
17219    }
17220
17221    /// Generate a unary function, using the original name if available for round-trip preservation
17222    fn generate_unary_func(
17223        &mut self,
17224        default_name: &str,
17225        f: &crate::expressions::UnaryFunc,
17226    ) -> Result<()> {
17227        let name = f.original_name.as_deref().unwrap_or(default_name);
17228        self.write_keyword(name);
17229        self.write("(");
17230        self.generate_expression(&f.this)?;
17231        self.write(")");
17232        Ok(())
17233    }
17234
17235    /// Generate SQRT/CBRT - always use function form (matches Python SQLGlot normalization)
17236    fn generate_sqrt_cbrt(
17237        &mut self,
17238        f: &crate::expressions::UnaryFunc,
17239        func_name: &str,
17240        _op: &str,
17241    ) -> Result<()> {
17242        // Python SQLGlot normalizes |/ and ||/ to SQRT() and CBRT()
17243        // Always use function syntax for consistency
17244        self.write_keyword(func_name);
17245        self.write("(");
17246        self.generate_expression(&f.this)?;
17247        self.write(")");
17248        Ok(())
17249    }
17250
17251    fn generate_binary_func(
17252        &mut self,
17253        name: &str,
17254        arg1: &Expression,
17255        arg2: &Expression,
17256    ) -> Result<()> {
17257        self.write_keyword(name);
17258        self.write("(");
17259        self.generate_expression(arg1)?;
17260        self.write(", ");
17261        self.generate_expression(arg2)?;
17262        self.write(")");
17263        Ok(())
17264    }
17265
17266    /// Generate CHAR/CHR function with optional USING charset
17267    /// e.g., CHAR(77, 77.3, '77.3' USING utf8mb4)
17268    /// e.g., CHR(187 USING NCHAR_CS) -- Oracle
17269    fn generate_char_func(&mut self, f: &crate::expressions::CharFunc) -> Result<()> {
17270        // Use stored name if available, otherwise default to CHAR
17271        let func_name = f.name.as_deref().unwrap_or("CHAR");
17272        self.write_keyword(func_name);
17273        self.write("(");
17274        for (i, arg) in f.args.iter().enumerate() {
17275            if i > 0 {
17276                self.write(", ");
17277            }
17278            self.generate_expression(arg)?;
17279        }
17280        if let Some(ref charset) = f.charset {
17281            self.write(" ");
17282            self.write_keyword("USING");
17283            self.write(" ");
17284            self.write(charset);
17285        }
17286        self.write(")");
17287        Ok(())
17288    }
17289
17290    fn generate_power(&mut self, f: &BinaryFunc) -> Result<()> {
17291        use crate::dialects::DialectType;
17292
17293        match self.config.dialect {
17294            Some(DialectType::Teradata) => {
17295                // Teradata uses ** operator for exponentiation
17296                self.generate_expression(&f.this)?;
17297                self.write(" ** ");
17298                self.generate_expression(&f.expression)?;
17299                Ok(())
17300            }
17301            _ => {
17302                // Other dialects use POWER function
17303                self.generate_binary_func("POWER", &f.this, &f.expression)
17304            }
17305        }
17306    }
17307
17308    fn generate_vararg_func(&mut self, name: &str, args: &[Expression]) -> Result<()> {
17309        self.write_func_name(name);
17310        self.write("(");
17311        for (i, arg) in args.iter().enumerate() {
17312            if i > 0 {
17313                self.write(", ");
17314            }
17315            self.generate_expression(arg)?;
17316        }
17317        self.write(")");
17318        Ok(())
17319    }
17320
17321    // String function generators
17322
17323    fn generate_concat_ws(&mut self, f: &ConcatWs) -> Result<()> {
17324        self.write_keyword("CONCAT_WS");
17325        self.write("(");
17326        self.generate_expression(&f.separator)?;
17327        for expr in &f.expressions {
17328            self.write(", ");
17329            self.generate_expression(expr)?;
17330        }
17331        self.write(")");
17332        Ok(())
17333    }
17334
17335    fn generate_substring(&mut self, f: &SubstringFunc) -> Result<()> {
17336        // Oracle uses SUBSTR; most others use SUBSTRING
17337        let is_oracle = matches!(self.config.dialect, Some(DialectType::Oracle));
17338        if is_oracle {
17339            self.write_keyword("SUBSTR");
17340        } else {
17341            self.write_keyword("SUBSTRING");
17342        }
17343        self.write("(");
17344        self.generate_expression(&f.this)?;
17345        // PostgreSQL always uses FROM/FOR syntax
17346        let force_from_for = matches!(self.config.dialect, Some(DialectType::PostgreSQL));
17347        // Spark/Hive use comma syntax, not FROM/FOR syntax
17348        let use_comma_syntax = matches!(
17349            self.config.dialect,
17350            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
17351        );
17352        if (f.from_for_syntax || force_from_for) && !use_comma_syntax {
17353            // SQL standard syntax: SUBSTRING(str FROM pos FOR len)
17354            self.write_space();
17355            self.write_keyword("FROM");
17356            self.write_space();
17357            self.generate_expression(&f.start)?;
17358            if let Some(length) = &f.length {
17359                self.write_space();
17360                self.write_keyword("FOR");
17361                self.write_space();
17362                self.generate_expression(length)?;
17363            }
17364        } else {
17365            // Comma-separated syntax: SUBSTRING(str, pos, len) or SUBSTR(str, pos, len)
17366            self.write(", ");
17367            self.generate_expression(&f.start)?;
17368            if let Some(length) = &f.length {
17369                self.write(", ");
17370                self.generate_expression(length)?;
17371            }
17372        }
17373        self.write(")");
17374        Ok(())
17375    }
17376
17377    fn generate_overlay(&mut self, f: &OverlayFunc) -> Result<()> {
17378        self.write_keyword("OVERLAY");
17379        self.write("(");
17380        self.generate_expression(&f.this)?;
17381        self.write_space();
17382        self.write_keyword("PLACING");
17383        self.write_space();
17384        self.generate_expression(&f.replacement)?;
17385        self.write_space();
17386        self.write_keyword("FROM");
17387        self.write_space();
17388        self.generate_expression(&f.from)?;
17389        if let Some(length) = &f.length {
17390            self.write_space();
17391            self.write_keyword("FOR");
17392            self.write_space();
17393            self.generate_expression(length)?;
17394        }
17395        self.write(")");
17396        Ok(())
17397    }
17398
17399    fn generate_trim(&mut self, f: &TrimFunc) -> Result<()> {
17400        // Special case: TRIM(LEADING str) -> LTRIM(str), TRIM(TRAILING str) -> RTRIM(str)
17401        // when no characters are specified (PostgreSQL style)
17402        if f.position_explicit && f.characters.is_none() {
17403            match f.position {
17404                TrimPosition::Leading => {
17405                    self.write_keyword("LTRIM");
17406                    self.write("(");
17407                    self.generate_expression(&f.this)?;
17408                    self.write(")");
17409                    return Ok(());
17410                }
17411                TrimPosition::Trailing => {
17412                    self.write_keyword("RTRIM");
17413                    self.write("(");
17414                    self.generate_expression(&f.this)?;
17415                    self.write(")");
17416                    return Ok(());
17417                }
17418                TrimPosition::Both => {
17419                    // TRIM(BOTH str) -> BTRIM(str) in PostgreSQL, but TRIM(str) is more standard
17420                    // Fall through to standard TRIM handling
17421                }
17422            }
17423        }
17424
17425        self.write_keyword("TRIM");
17426        self.write("(");
17427        // When BOTH is specified without trim characters, simplify to just TRIM(str)
17428        // Force standard syntax for dialects that require it (Hive, Spark, Databricks, ClickHouse)
17429        let force_standard = f.characters.is_some()
17430            && !f.sql_standard_syntax
17431            && matches!(
17432                self.config.dialect,
17433                Some(DialectType::Hive)
17434                    | Some(DialectType::Spark)
17435                    | Some(DialectType::Databricks)
17436                    | Some(DialectType::ClickHouse)
17437            );
17438        let use_standard = (f.sql_standard_syntax || force_standard)
17439            && !(f.position_explicit
17440                && f.characters.is_none()
17441                && matches!(f.position, TrimPosition::Both));
17442        if use_standard {
17443            // SQL standard syntax: TRIM(BOTH chars FROM str)
17444            // Only output position if it was explicitly specified
17445            if f.position_explicit {
17446                match f.position {
17447                    TrimPosition::Both => self.write_keyword("BOTH"),
17448                    TrimPosition::Leading => self.write_keyword("LEADING"),
17449                    TrimPosition::Trailing => self.write_keyword("TRAILING"),
17450                }
17451                self.write_space();
17452            }
17453            if let Some(chars) = &f.characters {
17454                self.generate_expression(chars)?;
17455                self.write_space();
17456            }
17457            self.write_keyword("FROM");
17458            self.write_space();
17459            self.generate_expression(&f.this)?;
17460        } else {
17461            // Simple function syntax: TRIM(str) or TRIM(str, chars)
17462            self.generate_expression(&f.this)?;
17463            if let Some(chars) = &f.characters {
17464                self.write(", ");
17465                self.generate_expression(chars)?;
17466            }
17467        }
17468        self.write(")");
17469        Ok(())
17470    }
17471
17472    fn generate_replace(&mut self, f: &ReplaceFunc) -> Result<()> {
17473        self.write_keyword("REPLACE");
17474        self.write("(");
17475        self.generate_expression(&f.this)?;
17476        self.write(", ");
17477        self.generate_expression(&f.old)?;
17478        self.write(", ");
17479        self.generate_expression(&f.new)?;
17480        self.write(")");
17481        Ok(())
17482    }
17483
17484    fn generate_left_right(&mut self, name: &str, f: &LeftRightFunc) -> Result<()> {
17485        self.write_keyword(name);
17486        self.write("(");
17487        self.generate_expression(&f.this)?;
17488        self.write(", ");
17489        self.generate_expression(&f.length)?;
17490        self.write(")");
17491        Ok(())
17492    }
17493
17494    fn generate_repeat(&mut self, f: &RepeatFunc) -> Result<()> {
17495        self.write_keyword("REPEAT");
17496        self.write("(");
17497        self.generate_expression(&f.this)?;
17498        self.write(", ");
17499        self.generate_expression(&f.times)?;
17500        self.write(")");
17501        Ok(())
17502    }
17503
17504    fn generate_pad(&mut self, name: &str, f: &PadFunc) -> Result<()> {
17505        self.write_keyword(name);
17506        self.write("(");
17507        self.generate_expression(&f.this)?;
17508        self.write(", ");
17509        self.generate_expression(&f.length)?;
17510        if let Some(fill) = &f.fill {
17511            self.write(", ");
17512            self.generate_expression(fill)?;
17513        }
17514        self.write(")");
17515        Ok(())
17516    }
17517
17518    fn generate_split(&mut self, f: &SplitFunc) -> Result<()> {
17519        self.write_keyword("SPLIT");
17520        self.write("(");
17521        self.generate_expression(&f.this)?;
17522        self.write(", ");
17523        self.generate_expression(&f.delimiter)?;
17524        self.write(")");
17525        Ok(())
17526    }
17527
17528    fn generate_regexp_like(&mut self, f: &RegexpFunc) -> Result<()> {
17529        use crate::dialects::DialectType;
17530        // PostgreSQL uses ~ operator for regex matching
17531        if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) && f.flags.is_none() {
17532            self.generate_expression(&f.this)?;
17533            self.write(" ~ ");
17534            self.generate_expression(&f.pattern)?;
17535        } else if matches!(
17536            self.config.dialect,
17537            Some(DialectType::SingleStore)
17538                | Some(DialectType::Spark)
17539                | Some(DialectType::Hive)
17540                | Some(DialectType::Databricks)
17541        ) && f.flags.is_none()
17542        {
17543            // SingleStore/Spark/Hive/Databricks use RLIKE infix operator
17544            self.generate_expression(&f.this)?;
17545            self.write_keyword(" RLIKE ");
17546            self.generate_expression(&f.pattern)?;
17547        } else if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
17548            // StarRocks uses REGEXP function syntax
17549            self.write_keyword("REGEXP");
17550            self.write("(");
17551            self.generate_expression(&f.this)?;
17552            self.write(", ");
17553            self.generate_expression(&f.pattern)?;
17554            if let Some(flags) = &f.flags {
17555                self.write(", ");
17556                self.generate_expression(flags)?;
17557            }
17558            self.write(")");
17559        } else {
17560            self.write_keyword("REGEXP_LIKE");
17561            self.write("(");
17562            self.generate_expression(&f.this)?;
17563            self.write(", ");
17564            self.generate_expression(&f.pattern)?;
17565            if let Some(flags) = &f.flags {
17566                self.write(", ");
17567                self.generate_expression(flags)?;
17568            }
17569            self.write(")");
17570        }
17571        Ok(())
17572    }
17573
17574    fn generate_regexp_replace(&mut self, f: &RegexpReplaceFunc) -> Result<()> {
17575        self.write_keyword("REGEXP_REPLACE");
17576        self.write("(");
17577        self.generate_expression(&f.this)?;
17578        self.write(", ");
17579        self.generate_expression(&f.pattern)?;
17580        self.write(", ");
17581        self.generate_expression(&f.replacement)?;
17582        if let Some(flags) = &f.flags {
17583            self.write(", ");
17584            self.generate_expression(flags)?;
17585        }
17586        self.write(")");
17587        Ok(())
17588    }
17589
17590    fn generate_regexp_extract(&mut self, f: &RegexpExtractFunc) -> Result<()> {
17591        self.write_keyword("REGEXP_EXTRACT");
17592        self.write("(");
17593        self.generate_expression(&f.this)?;
17594        self.write(", ");
17595        self.generate_expression(&f.pattern)?;
17596        if let Some(group) = &f.group {
17597            self.write(", ");
17598            self.generate_expression(group)?;
17599        }
17600        self.write(")");
17601        Ok(())
17602    }
17603
17604    // Math function generators
17605
17606    fn generate_round(&mut self, f: &RoundFunc) -> Result<()> {
17607        self.write_keyword("ROUND");
17608        self.write("(");
17609        self.generate_expression(&f.this)?;
17610        if let Some(decimals) = &f.decimals {
17611            self.write(", ");
17612            self.generate_expression(decimals)?;
17613        }
17614        self.write(")");
17615        Ok(())
17616    }
17617
17618    fn generate_floor(&mut self, f: &FloorFunc) -> Result<()> {
17619        self.write_keyword("FLOOR");
17620        self.write("(");
17621        self.generate_expression(&f.this)?;
17622        // Handle Druid-style FLOOR(time TO unit) syntax
17623        if let Some(to) = &f.to {
17624            self.write(" ");
17625            self.write_keyword("TO");
17626            self.write(" ");
17627            self.generate_expression(to)?;
17628        } else if let Some(scale) = &f.scale {
17629            self.write(", ");
17630            self.generate_expression(scale)?;
17631        }
17632        self.write(")");
17633        Ok(())
17634    }
17635
17636    fn generate_ceil(&mut self, f: &CeilFunc) -> Result<()> {
17637        self.write_keyword("CEIL");
17638        self.write("(");
17639        self.generate_expression(&f.this)?;
17640        // Handle Druid-style CEIL(time TO unit) syntax
17641        if let Some(to) = &f.to {
17642            self.write(" ");
17643            self.write_keyword("TO");
17644            self.write(" ");
17645            self.generate_expression(to)?;
17646        } else if let Some(decimals) = &f.decimals {
17647            self.write(", ");
17648            self.generate_expression(decimals)?;
17649        }
17650        self.write(")");
17651        Ok(())
17652    }
17653
17654    fn generate_log(&mut self, f: &LogFunc) -> Result<()> {
17655        use crate::expressions::Literal;
17656
17657        if let Some(base) = &f.base {
17658            // Check for LOG_BASE_FIRST = None dialects (Presto, Trino, ClickHouse, Athena)
17659            // These dialects use LOG2()/LOG10() instead of LOG(base, value)
17660            if self.is_log_base_none() {
17661                if matches!(base, Expression::Literal(Literal::Number(s)) if s == "2") {
17662                    self.write_func_name("LOG2");
17663                    self.write("(");
17664                    self.generate_expression(&f.this)?;
17665                    self.write(")");
17666                    return Ok(());
17667                } else if matches!(base, Expression::Literal(Literal::Number(s)) if s == "10") {
17668                    self.write_func_name("LOG10");
17669                    self.write("(");
17670                    self.generate_expression(&f.this)?;
17671                    self.write(")");
17672                    return Ok(());
17673                }
17674                // Other bases: fall through to LOG(base, value) — best effort
17675            }
17676
17677            self.write_func_name("LOG");
17678            self.write("(");
17679            if self.is_log_value_first() {
17680                // BigQuery, TSQL, Tableau, Fabric: LOG(value, base)
17681                self.generate_expression(&f.this)?;
17682                self.write(", ");
17683                self.generate_expression(base)?;
17684            } else {
17685                // Default (PostgreSQL, etc.): LOG(base, value)
17686                self.generate_expression(base)?;
17687                self.write(", ");
17688                self.generate_expression(&f.this)?;
17689            }
17690            self.write(")");
17691        } else {
17692            // Single arg: LOG(x) — unspecified base (log base 10 in default dialect)
17693            self.write_func_name("LOG");
17694            self.write("(");
17695            self.generate_expression(&f.this)?;
17696            self.write(")");
17697        }
17698        Ok(())
17699    }
17700
17701    /// Whether the target dialect uses LOG(value, base) order (value first).
17702    /// BigQuery, TSQL, Tableau, Fabric use LOG(value, base).
17703    fn is_log_value_first(&self) -> bool {
17704        use crate::dialects::DialectType;
17705        matches!(
17706            self.config.dialect,
17707            Some(DialectType::BigQuery)
17708                | Some(DialectType::TSQL)
17709                | Some(DialectType::Tableau)
17710                | Some(DialectType::Fabric)
17711        )
17712    }
17713
17714    /// Whether the target dialect has LOG_BASE_FIRST = None (uses LOG2/LOG10 instead).
17715    /// Presto, Trino, ClickHouse, Athena.
17716    fn is_log_base_none(&self) -> bool {
17717        use crate::dialects::DialectType;
17718        matches!(
17719            self.config.dialect,
17720            Some(DialectType::Presto)
17721                | Some(DialectType::Trino)
17722                | Some(DialectType::ClickHouse)
17723                | Some(DialectType::Athena)
17724        )
17725    }
17726
17727    // Date/time function generators
17728
17729    fn generate_current_time(&mut self, f: &CurrentTime) -> Result<()> {
17730        self.write_keyword("CURRENT_TIME");
17731        if let Some(precision) = f.precision {
17732            self.write(&format!("({})", precision));
17733        }
17734        Ok(())
17735    }
17736
17737    fn generate_current_timestamp(&mut self, f: &CurrentTimestamp) -> Result<()> {
17738        use crate::dialects::DialectType;
17739
17740        // Oracle/Redshift SYSDATE handling
17741        if f.sysdate {
17742            match self.config.dialect {
17743                Some(DialectType::Oracle) | Some(DialectType::Redshift) => {
17744                    self.write_keyword("SYSDATE");
17745                    return Ok(());
17746                }
17747                Some(DialectType::Snowflake) => {
17748                    // Snowflake uses SYSDATE() function
17749                    self.write_keyword("SYSDATE");
17750                    self.write("()");
17751                    return Ok(());
17752                }
17753                _ => {
17754                    // Other dialects use CURRENT_TIMESTAMP for SYSDATE
17755                }
17756            }
17757        }
17758
17759        self.write_keyword("CURRENT_TIMESTAMP");
17760        // MySQL, Spark, Hive always use CURRENT_TIMESTAMP() with parentheses
17761        if let Some(precision) = f.precision {
17762            self.write(&format!("({})", precision));
17763        } else if matches!(
17764            self.config.dialect,
17765            Some(crate::dialects::DialectType::MySQL)
17766                | Some(crate::dialects::DialectType::SingleStore)
17767                | Some(crate::dialects::DialectType::TiDB)
17768                | Some(crate::dialects::DialectType::Spark)
17769                | Some(crate::dialects::DialectType::Hive)
17770                | Some(crate::dialects::DialectType::Databricks)
17771                | Some(crate::dialects::DialectType::ClickHouse)
17772                | Some(crate::dialects::DialectType::BigQuery)
17773                | Some(crate::dialects::DialectType::Snowflake)
17774        ) {
17775            self.write("()");
17776        }
17777        Ok(())
17778    }
17779
17780    fn generate_at_time_zone(&mut self, f: &AtTimeZone) -> Result<()> {
17781        // Exasol uses CONVERT_TZ(timestamp, 'UTC', zone) instead of AT TIME ZONE
17782        if self.config.dialect == Some(DialectType::Exasol) {
17783            self.write_keyword("CONVERT_TZ");
17784            self.write("(");
17785            self.generate_expression(&f.this)?;
17786            self.write(", 'UTC', ");
17787            self.generate_expression(&f.zone)?;
17788            self.write(")");
17789            return Ok(());
17790        }
17791
17792        self.generate_expression(&f.this)?;
17793        self.write_space();
17794        self.write_keyword("AT TIME ZONE");
17795        self.write_space();
17796        self.generate_expression(&f.zone)?;
17797        Ok(())
17798    }
17799
17800    fn generate_date_add(&mut self, f: &DateAddFunc, name: &str) -> Result<()> {
17801        use crate::dialects::DialectType;
17802
17803        // Presto/Trino use DATE_ADD('unit', interval, date) format
17804        // with the interval cast to BIGINT when needed
17805        let is_presto_like = matches!(
17806            self.config.dialect,
17807            Some(DialectType::Presto) | Some(DialectType::Trino)
17808        );
17809
17810        if is_presto_like {
17811            self.write_keyword(name);
17812            self.write("(");
17813            // Unit as string literal
17814            self.write("'");
17815            self.write_simple_interval_unit(&f.unit, false);
17816            self.write("'");
17817            self.write(", ");
17818            // Interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
17819            let needs_cast = !self.returns_integer_type(&f.interval);
17820            if needs_cast {
17821                self.write_keyword("CAST");
17822                self.write("(");
17823            }
17824            self.generate_expression(&f.interval)?;
17825            if needs_cast {
17826                self.write_space();
17827                self.write_keyword("AS");
17828                self.write_space();
17829                self.write_keyword("BIGINT");
17830                self.write(")");
17831            }
17832            self.write(", ");
17833            self.generate_expression(&f.this)?;
17834            self.write(")");
17835        } else {
17836            self.write_keyword(name);
17837            self.write("(");
17838            self.generate_expression(&f.this)?;
17839            self.write(", ");
17840            self.write_keyword("INTERVAL");
17841            self.write_space();
17842            self.generate_expression(&f.interval)?;
17843            self.write_space();
17844            self.write_simple_interval_unit(&f.unit, false); // Use singular form for DATEADD
17845            self.write(")");
17846        }
17847        Ok(())
17848    }
17849
17850    /// Check if an expression returns an integer type (doesn't need cast to BIGINT in Presto DATE_ADD)
17851    /// This is a heuristic to avoid full type inference
17852    fn returns_integer_type(&self, expr: &Expression) -> bool {
17853        use crate::expressions::{DataType, Literal};
17854        match expr {
17855            // Integer literals (no decimal point)
17856            Expression::Literal(Literal::Number(n)) => !n.contains('.'),
17857
17858            // FLOOR(x) returns integer if x is integer
17859            Expression::Floor(f) => self.returns_integer_type(&f.this),
17860
17861            // ROUND(x) returns integer if x is integer
17862            Expression::Round(f) => {
17863                // Only if no decimals arg or it's returning an integer
17864                f.decimals.is_none() && self.returns_integer_type(&f.this)
17865            }
17866
17867            // SIGN returns integer if input is integer
17868            Expression::Sign(f) => self.returns_integer_type(&f.this),
17869
17870            // ABS returns the same type as input
17871            Expression::Abs(f) => self.returns_integer_type(&f.this),
17872
17873            // Arithmetic operations on integers return integers
17874            Expression::Mul(op) => {
17875                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
17876            }
17877            Expression::Add(op) => {
17878                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
17879            }
17880            Expression::Sub(op) => {
17881                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
17882            }
17883            Expression::Mod(op) => self.returns_integer_type(&op.left),
17884
17885            // CAST(x AS BIGINT/INT/INTEGER/SMALLINT/TINYINT) returns integer
17886            Expression::Cast(c) => matches!(
17887                &c.to,
17888                DataType::BigInt { .. }
17889                    | DataType::Int { .. }
17890                    | DataType::SmallInt { .. }
17891                    | DataType::TinyInt { .. }
17892            ),
17893
17894            // Negation: -x returns integer if x is integer
17895            Expression::Neg(op) => self.returns_integer_type(&op.this),
17896
17897            // Parenthesized expression
17898            Expression::Paren(p) => self.returns_integer_type(&p.this),
17899
17900            // Column references and most expressions are assumed to need casting
17901            // since we don't have full type information
17902            _ => false,
17903        }
17904    }
17905
17906    fn generate_datediff(&mut self, f: &DateDiffFunc) -> Result<()> {
17907        self.write_keyword("DATEDIFF");
17908        self.write("(");
17909        if let Some(unit) = &f.unit {
17910            self.write_simple_interval_unit(unit, false); // Use singular form for DATEDIFF
17911            self.write(", ");
17912        }
17913        self.generate_expression(&f.this)?;
17914        self.write(", ");
17915        self.generate_expression(&f.expression)?;
17916        self.write(")");
17917        Ok(())
17918    }
17919
17920    fn generate_date_trunc(&mut self, f: &DateTruncFunc) -> Result<()> {
17921        self.write_keyword("DATE_TRUNC");
17922        self.write("('");
17923        self.write_datetime_field(&f.unit);
17924        self.write("', ");
17925        self.generate_expression(&f.this)?;
17926        self.write(")");
17927        Ok(())
17928    }
17929
17930    fn generate_last_day(&mut self, f: &LastDayFunc) -> Result<()> {
17931        use crate::dialects::DialectType;
17932        use crate::expressions::DateTimeField;
17933
17934        self.write_keyword("LAST_DAY");
17935        self.write("(");
17936        self.generate_expression(&f.this)?;
17937        if let Some(unit) = &f.unit {
17938            self.write(", ");
17939            // BigQuery: strip week-start modifier from WEEK(SUNDAY), WEEK(MONDAY), etc.
17940            // WEEK(SUNDAY) -> WEEK
17941            if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
17942                if let DateTimeField::WeekWithModifier(_) = unit {
17943                    self.write_keyword("WEEK");
17944                } else {
17945                    self.write_datetime_field(unit);
17946                }
17947            } else {
17948                self.write_datetime_field(unit);
17949            }
17950        }
17951        self.write(")");
17952        Ok(())
17953    }
17954
17955    fn generate_extract(&mut self, f: &ExtractFunc) -> Result<()> {
17956        // TSQL/Fabric use DATEPART(part, expr) instead of EXTRACT(part FROM expr)
17957        if matches!(
17958            self.config.dialect,
17959            Some(DialectType::TSQL) | Some(DialectType::Fabric)
17960        ) {
17961            self.write_keyword("DATEPART");
17962            self.write("(");
17963            self.write_datetime_field(&f.field);
17964            self.write(", ");
17965            self.generate_expression(&f.this)?;
17966            self.write(")");
17967            return Ok(());
17968        }
17969        self.write_keyword("EXTRACT");
17970        self.write("(");
17971        // Hive/Spark use lowercase datetime fields in EXTRACT
17972        if matches!(
17973            self.config.dialect,
17974            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
17975        ) {
17976            self.write_datetime_field_lower(&f.field);
17977        } else {
17978            self.write_datetime_field(&f.field);
17979        }
17980        self.write_space();
17981        self.write_keyword("FROM");
17982        self.write_space();
17983        self.generate_expression(&f.this)?;
17984        self.write(")");
17985        Ok(())
17986    }
17987
17988    fn generate_to_date(&mut self, f: &ToDateFunc) -> Result<()> {
17989        self.write_keyword("TO_DATE");
17990        self.write("(");
17991        self.generate_expression(&f.this)?;
17992        if let Some(format) = &f.format {
17993            self.write(", ");
17994            self.generate_expression(format)?;
17995        }
17996        self.write(")");
17997        Ok(())
17998    }
17999
18000    fn generate_to_timestamp(&mut self, f: &ToTimestampFunc) -> Result<()> {
18001        self.write_keyword("TO_TIMESTAMP");
18002        self.write("(");
18003        self.generate_expression(&f.this)?;
18004        if let Some(format) = &f.format {
18005            self.write(", ");
18006            self.generate_expression(format)?;
18007        }
18008        self.write(")");
18009        Ok(())
18010    }
18011
18012    // Control flow function generators
18013
18014    fn generate_if_func(&mut self, f: &IfFunc) -> Result<()> {
18015        use crate::dialects::DialectType;
18016
18017        // Generic mode: normalize IF to CASE WHEN
18018        if self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic) {
18019            self.write_keyword("CASE WHEN");
18020            self.write_space();
18021            self.generate_expression(&f.condition)?;
18022            self.write_space();
18023            self.write_keyword("THEN");
18024            self.write_space();
18025            self.generate_expression(&f.true_value)?;
18026            if let Some(false_val) = &f.false_value {
18027                self.write_space();
18028                self.write_keyword("ELSE");
18029                self.write_space();
18030                self.generate_expression(false_val)?;
18031            }
18032            self.write_space();
18033            self.write_keyword("END");
18034            return Ok(());
18035        }
18036
18037        // Exasol uses IF condition THEN true_value ELSE false_value ENDIF syntax
18038        if self.config.dialect == Some(DialectType::Exasol) {
18039            self.write_keyword("IF");
18040            self.write_space();
18041            self.generate_expression(&f.condition)?;
18042            self.write_space();
18043            self.write_keyword("THEN");
18044            self.write_space();
18045            self.generate_expression(&f.true_value)?;
18046            if let Some(false_val) = &f.false_value {
18047                self.write_space();
18048                self.write_keyword("ELSE");
18049                self.write_space();
18050                self.generate_expression(false_val)?;
18051            }
18052            self.write_space();
18053            self.write_keyword("ENDIF");
18054            return Ok(());
18055        }
18056
18057        // Choose function name based on target dialect
18058        let func_name = match self.config.dialect {
18059            Some(DialectType::Snowflake) => "IFF",
18060            Some(DialectType::SQLite) | Some(DialectType::TSQL) => "IIF",
18061            Some(DialectType::Drill) => "`IF`",
18062            _ => "IF",
18063        };
18064        self.write(func_name);
18065        self.write("(");
18066        self.generate_expression(&f.condition)?;
18067        self.write(", ");
18068        self.generate_expression(&f.true_value)?;
18069        if let Some(false_val) = &f.false_value {
18070            self.write(", ");
18071            self.generate_expression(false_val)?;
18072        }
18073        self.write(")");
18074        Ok(())
18075    }
18076
18077    fn generate_nvl2(&mut self, f: &Nvl2Func) -> Result<()> {
18078        self.write_keyword("NVL2");
18079        self.write("(");
18080        self.generate_expression(&f.this)?;
18081        self.write(", ");
18082        self.generate_expression(&f.true_value)?;
18083        self.write(", ");
18084        self.generate_expression(&f.false_value)?;
18085        self.write(")");
18086        Ok(())
18087    }
18088
18089    // Typed aggregate function generators
18090
18091    fn generate_count(&mut self, f: &CountFunc) -> Result<()> {
18092        // Use normalize_functions for COUNT to respect ClickHouse case preservation
18093        let count_name = match self.config.normalize_functions {
18094            NormalizeFunctions::Upper => "COUNT".to_string(),
18095            NormalizeFunctions::Lower => "count".to_string(),
18096            NormalizeFunctions::None => f
18097                .original_name
18098                .clone()
18099                .unwrap_or_else(|| "COUNT".to_string()),
18100        };
18101        self.write(&count_name);
18102        self.write("(");
18103        if f.distinct {
18104            self.write_keyword("DISTINCT");
18105            self.write_space();
18106        }
18107        if f.star {
18108            self.write("*");
18109        } else if let Some(ref expr) = f.this {
18110            // For COUNT(DISTINCT a, b), unwrap the Tuple to avoid extra parentheses
18111            if let Expression::Tuple(tuple) = expr {
18112                // Check if we need to transform multi-arg COUNT DISTINCT
18113                // When dialect doesn't support multi_arg_distinct, transform:
18114                // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
18115                let needs_transform =
18116                    f.distinct && tuple.expressions.len() > 1 && !self.config.multi_arg_distinct;
18117
18118                if needs_transform {
18119                    // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
18120                    self.write_keyword("CASE");
18121                    for e in &tuple.expressions {
18122                        self.write_space();
18123                        self.write_keyword("WHEN");
18124                        self.write_space();
18125                        self.generate_expression(e)?;
18126                        self.write_space();
18127                        self.write_keyword("IS NULL THEN NULL");
18128                    }
18129                    self.write_space();
18130                    self.write_keyword("ELSE");
18131                    self.write(" (");
18132                    for (i, e) in tuple.expressions.iter().enumerate() {
18133                        if i > 0 {
18134                            self.write(", ");
18135                        }
18136                        self.generate_expression(e)?;
18137                    }
18138                    self.write(")");
18139                    self.write_space();
18140                    self.write_keyword("END");
18141                } else {
18142                    for (i, e) in tuple.expressions.iter().enumerate() {
18143                        if i > 0 {
18144                            self.write(", ");
18145                        }
18146                        self.generate_expression(e)?;
18147                    }
18148                }
18149            } else {
18150                self.generate_expression(expr)?;
18151            }
18152        }
18153        // RESPECT NULLS / IGNORE NULLS
18154        if let Some(ignore) = f.ignore_nulls {
18155            self.write_space();
18156            if ignore {
18157                self.write_keyword("IGNORE NULLS");
18158            } else {
18159                self.write_keyword("RESPECT NULLS");
18160            }
18161        }
18162        self.write(")");
18163        if let Some(ref filter) = f.filter {
18164            self.write_space();
18165            self.write_keyword("FILTER");
18166            self.write("(");
18167            self.write_keyword("WHERE");
18168            self.write_space();
18169            self.generate_expression(filter)?;
18170            self.write(")");
18171        }
18172        Ok(())
18173    }
18174
18175    fn generate_agg_func(&mut self, name: &str, f: &AggFunc) -> Result<()> {
18176        // Apply function name normalization based on config
18177        let func_name = match self.config.normalize_functions {
18178            NormalizeFunctions::Upper => name.to_uppercase(),
18179            NormalizeFunctions::Lower => name.to_lowercase(),
18180            NormalizeFunctions::None => {
18181                // Use the original function name from parsing if available,
18182                // otherwise fall back to lowercase of the hardcoded constant
18183                if let Some(ref original) = f.name {
18184                    original.clone()
18185                } else {
18186                    name.to_lowercase()
18187                }
18188            }
18189        };
18190        self.write(&func_name);
18191        self.write("(");
18192        if f.distinct {
18193            self.write_keyword("DISTINCT");
18194            self.write_space();
18195        }
18196        // Skip generating the expression if it's a NULL placeholder for zero-arg aggregates like MODE()
18197        if !matches!(f.this, Expression::Null(_)) {
18198            self.generate_expression(&f.this)?;
18199        }
18200        // Generate IGNORE NULLS / RESPECT NULLS inside parens if config says so (BigQuery style)
18201        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
18202        if self.config.ignore_nulls_in_func
18203            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
18204        {
18205            match f.ignore_nulls {
18206                Some(true) => {
18207                    self.write_space();
18208                    self.write_keyword("IGNORE NULLS");
18209                }
18210                Some(false) => {
18211                    self.write_space();
18212                    self.write_keyword("RESPECT NULLS");
18213                }
18214                None => {}
18215            }
18216        }
18217        // Generate HAVING MAX/MIN if present (BigQuery syntax)
18218        // e.g., ANY_VALUE(fruit HAVING MAX sold)
18219        if let Some((ref expr, is_max)) = f.having_max {
18220            self.write_space();
18221            self.write_keyword("HAVING");
18222            self.write_space();
18223            if is_max {
18224                self.write_keyword("MAX");
18225            } else {
18226                self.write_keyword("MIN");
18227            }
18228            self.write_space();
18229            self.generate_expression(expr)?;
18230        }
18231        // Generate ORDER BY if present (for aggregates like ARRAY_AGG(x ORDER BY y))
18232        if !f.order_by.is_empty() {
18233            self.write_space();
18234            self.write_keyword("ORDER BY");
18235            self.write_space();
18236            for (i, ord) in f.order_by.iter().enumerate() {
18237                if i > 0 {
18238                    self.write(", ");
18239                }
18240                self.generate_ordered(ord)?;
18241            }
18242        }
18243        // Generate LIMIT if present (for aggregates like ARRAY_AGG(x ORDER BY y LIMIT 2))
18244        if let Some(ref limit) = f.limit {
18245            self.write_space();
18246            self.write_keyword("LIMIT");
18247            self.write_space();
18248            // Check if this is a Tuple representing LIMIT offset, count
18249            if let Expression::Tuple(t) = limit.as_ref() {
18250                if t.expressions.len() == 2 {
18251                    self.generate_expression(&t.expressions[0])?;
18252                    self.write(", ");
18253                    self.generate_expression(&t.expressions[1])?;
18254                } else {
18255                    self.generate_expression(limit)?;
18256                }
18257            } else {
18258                self.generate_expression(limit)?;
18259            }
18260        }
18261        self.write(")");
18262        // Generate IGNORE NULLS / RESPECT NULLS outside parens if config says so (standard style)
18263        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
18264        if !self.config.ignore_nulls_in_func
18265            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
18266        {
18267            match f.ignore_nulls {
18268                Some(true) => {
18269                    self.write_space();
18270                    self.write_keyword("IGNORE NULLS");
18271                }
18272                Some(false) => {
18273                    self.write_space();
18274                    self.write_keyword("RESPECT NULLS");
18275                }
18276                None => {}
18277            }
18278        }
18279        if let Some(ref filter) = f.filter {
18280            self.write_space();
18281            self.write_keyword("FILTER");
18282            self.write("(");
18283            self.write_keyword("WHERE");
18284            self.write_space();
18285            self.generate_expression(filter)?;
18286            self.write(")");
18287        }
18288        Ok(())
18289    }
18290
18291    fn generate_group_concat(&mut self, f: &GroupConcatFunc) -> Result<()> {
18292        self.write_keyword("GROUP_CONCAT");
18293        self.write("(");
18294        if f.distinct {
18295            self.write_keyword("DISTINCT");
18296            self.write_space();
18297        }
18298        self.generate_expression(&f.this)?;
18299        if let Some(ref order_by) = f.order_by {
18300            self.write_space();
18301            self.write_keyword("ORDER BY");
18302            self.write_space();
18303            for (i, ord) in order_by.iter().enumerate() {
18304                if i > 0 {
18305                    self.write(", ");
18306                }
18307                self.generate_ordered(ord)?;
18308            }
18309        }
18310        if let Some(ref sep) = f.separator {
18311            // SQLite uses GROUP_CONCAT(x, sep) syntax (comma-separated)
18312            // MySQL and others use GROUP_CONCAT(x SEPARATOR sep) syntax
18313            if matches!(
18314                self.config.dialect,
18315                Some(crate::dialects::DialectType::SQLite)
18316            ) {
18317                self.write(", ");
18318                self.generate_expression(sep)?;
18319            } else {
18320                self.write_space();
18321                self.write_keyword("SEPARATOR");
18322                self.write_space();
18323                self.generate_expression(sep)?;
18324            }
18325        }
18326        self.write(")");
18327        if let Some(ref filter) = f.filter {
18328            self.write_space();
18329            self.write_keyword("FILTER");
18330            self.write("(");
18331            self.write_keyword("WHERE");
18332            self.write_space();
18333            self.generate_expression(filter)?;
18334            self.write(")");
18335        }
18336        Ok(())
18337    }
18338
18339    fn generate_string_agg(&mut self, f: &StringAggFunc) -> Result<()> {
18340        let is_tsql = matches!(
18341            self.config.dialect,
18342            Some(crate::dialects::DialectType::TSQL)
18343        );
18344        self.write_keyword("STRING_AGG");
18345        self.write("(");
18346        if f.distinct {
18347            self.write_keyword("DISTINCT");
18348            self.write_space();
18349        }
18350        self.generate_expression(&f.this)?;
18351        if let Some(ref separator) = f.separator {
18352            self.write(", ");
18353            self.generate_expression(separator)?;
18354        }
18355        // For TSQL, ORDER BY goes in WITHIN GROUP clause after the closing paren
18356        if !is_tsql {
18357            if let Some(ref order_by) = f.order_by {
18358                self.write_space();
18359                self.write_keyword("ORDER BY");
18360                self.write_space();
18361                for (i, ord) in order_by.iter().enumerate() {
18362                    if i > 0 {
18363                        self.write(", ");
18364                    }
18365                    self.generate_ordered(ord)?;
18366                }
18367            }
18368        }
18369        if let Some(ref limit) = f.limit {
18370            self.write_space();
18371            self.write_keyword("LIMIT");
18372            self.write_space();
18373            self.generate_expression(limit)?;
18374        }
18375        self.write(")");
18376        // TSQL uses WITHIN GROUP (ORDER BY ...) after the function call
18377        if is_tsql {
18378            if let Some(ref order_by) = f.order_by {
18379                self.write_space();
18380                self.write_keyword("WITHIN GROUP");
18381                self.write(" (");
18382                self.write_keyword("ORDER BY");
18383                self.write_space();
18384                for (i, ord) in order_by.iter().enumerate() {
18385                    if i > 0 {
18386                        self.write(", ");
18387                    }
18388                    self.generate_ordered(ord)?;
18389                }
18390                self.write(")");
18391            }
18392        }
18393        if let Some(ref filter) = f.filter {
18394            self.write_space();
18395            self.write_keyword("FILTER");
18396            self.write("(");
18397            self.write_keyword("WHERE");
18398            self.write_space();
18399            self.generate_expression(filter)?;
18400            self.write(")");
18401        }
18402        Ok(())
18403    }
18404
18405    fn generate_listagg(&mut self, f: &ListAggFunc) -> Result<()> {
18406        use crate::dialects::DialectType;
18407        self.write_keyword("LISTAGG");
18408        self.write("(");
18409        if f.distinct {
18410            self.write_keyword("DISTINCT");
18411            self.write_space();
18412        }
18413        self.generate_expression(&f.this)?;
18414        if let Some(ref sep) = f.separator {
18415            self.write(", ");
18416            self.generate_expression(sep)?;
18417        } else if matches!(
18418            self.config.dialect,
18419            Some(DialectType::Trino) | Some(DialectType::Presto)
18420        ) {
18421            // Trino/Presto require explicit separator; default to ','
18422            self.write(", ','");
18423        }
18424        if let Some(ref overflow) = f.on_overflow {
18425            self.write_space();
18426            self.write_keyword("ON OVERFLOW");
18427            self.write_space();
18428            match overflow {
18429                ListAggOverflow::Error => self.write_keyword("ERROR"),
18430                ListAggOverflow::Truncate { filler, with_count } => {
18431                    self.write_keyword("TRUNCATE");
18432                    if let Some(ref fill) = filler {
18433                        self.write_space();
18434                        self.generate_expression(fill)?;
18435                    }
18436                    if *with_count {
18437                        self.write_space();
18438                        self.write_keyword("WITH COUNT");
18439                    } else {
18440                        self.write_space();
18441                        self.write_keyword("WITHOUT COUNT");
18442                    }
18443                }
18444            }
18445        }
18446        self.write(")");
18447        if let Some(ref order_by) = f.order_by {
18448            self.write_space();
18449            self.write_keyword("WITHIN GROUP");
18450            self.write(" (");
18451            self.write_keyword("ORDER BY");
18452            self.write_space();
18453            for (i, ord) in order_by.iter().enumerate() {
18454                if i > 0 {
18455                    self.write(", ");
18456                }
18457                self.generate_ordered(ord)?;
18458            }
18459            self.write(")");
18460        }
18461        if let Some(ref filter) = f.filter {
18462            self.write_space();
18463            self.write_keyword("FILTER");
18464            self.write("(");
18465            self.write_keyword("WHERE");
18466            self.write_space();
18467            self.generate_expression(filter)?;
18468            self.write(")");
18469        }
18470        Ok(())
18471    }
18472
18473    fn generate_sum_if(&mut self, f: &SumIfFunc) -> Result<()> {
18474        self.write_keyword("SUM_IF");
18475        self.write("(");
18476        self.generate_expression(&f.this)?;
18477        self.write(", ");
18478        self.generate_expression(&f.condition)?;
18479        self.write(")");
18480        if let Some(ref filter) = f.filter {
18481            self.write_space();
18482            self.write_keyword("FILTER");
18483            self.write("(");
18484            self.write_keyword("WHERE");
18485            self.write_space();
18486            self.generate_expression(filter)?;
18487            self.write(")");
18488        }
18489        Ok(())
18490    }
18491
18492    fn generate_approx_percentile(&mut self, f: &ApproxPercentileFunc) -> Result<()> {
18493        self.write_keyword("APPROX_PERCENTILE");
18494        self.write("(");
18495        self.generate_expression(&f.this)?;
18496        self.write(", ");
18497        self.generate_expression(&f.percentile)?;
18498        if let Some(ref acc) = f.accuracy {
18499            self.write(", ");
18500            self.generate_expression(acc)?;
18501        }
18502        self.write(")");
18503        if let Some(ref filter) = f.filter {
18504            self.write_space();
18505            self.write_keyword("FILTER");
18506            self.write("(");
18507            self.write_keyword("WHERE");
18508            self.write_space();
18509            self.generate_expression(filter)?;
18510            self.write(")");
18511        }
18512        Ok(())
18513    }
18514
18515    fn generate_percentile(&mut self, name: &str, f: &PercentileFunc) -> Result<()> {
18516        self.write_keyword(name);
18517        self.write("(");
18518        self.generate_expression(&f.percentile)?;
18519        self.write(")");
18520        if let Some(ref order_by) = f.order_by {
18521            self.write_space();
18522            self.write_keyword("WITHIN GROUP");
18523            self.write(" (");
18524            self.write_keyword("ORDER BY");
18525            self.write_space();
18526            self.generate_expression(&f.this)?;
18527            for ord in order_by.iter() {
18528                if ord.desc {
18529                    self.write_space();
18530                    self.write_keyword("DESC");
18531                }
18532            }
18533            self.write(")");
18534        }
18535        if let Some(ref filter) = f.filter {
18536            self.write_space();
18537            self.write_keyword("FILTER");
18538            self.write("(");
18539            self.write_keyword("WHERE");
18540            self.write_space();
18541            self.generate_expression(filter)?;
18542            self.write(")");
18543        }
18544        Ok(())
18545    }
18546
18547    // Window function generators
18548
18549    fn generate_ntile(&mut self, f: &NTileFunc) -> Result<()> {
18550        self.write_keyword("NTILE");
18551        self.write("(");
18552        if let Some(num_buckets) = &f.num_buckets {
18553            self.generate_expression(num_buckets)?;
18554        }
18555        if let Some(order_by) = &f.order_by {
18556            self.write_keyword(" ORDER BY ");
18557            for (i, ob) in order_by.iter().enumerate() {
18558                if i > 0 {
18559                    self.write(", ");
18560                }
18561                self.generate_ordered(ob)?;
18562            }
18563        }
18564        self.write(")");
18565        Ok(())
18566    }
18567
18568    fn generate_lead_lag(&mut self, name: &str, f: &LeadLagFunc) -> Result<()> {
18569        self.write_keyword(name);
18570        self.write("(");
18571        self.generate_expression(&f.this)?;
18572        if let Some(ref offset) = f.offset {
18573            self.write(", ");
18574            self.generate_expression(offset)?;
18575            if let Some(ref default) = f.default {
18576                self.write(", ");
18577                self.generate_expression(default)?;
18578            }
18579        }
18580        // IGNORE NULLS inside parens for dialects like BigQuery
18581        if f.ignore_nulls && self.config.ignore_nulls_in_func {
18582            self.write_space();
18583            self.write_keyword("IGNORE NULLS");
18584        }
18585        self.write(")");
18586        // IGNORE NULLS outside parens for other dialects
18587        if f.ignore_nulls && !self.config.ignore_nulls_in_func {
18588            self.write_space();
18589            self.write_keyword("IGNORE NULLS");
18590        }
18591        Ok(())
18592    }
18593
18594    fn generate_value_func(&mut self, name: &str, f: &ValueFunc) -> Result<()> {
18595        self.write_keyword(name);
18596        self.write("(");
18597        self.generate_expression(&f.this)?;
18598        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery
18599        if self.config.ignore_nulls_in_func {
18600            match f.ignore_nulls {
18601                Some(true) => {
18602                    self.write_space();
18603                    self.write_keyword("IGNORE NULLS");
18604                }
18605                Some(false) => {
18606                    self.write_space();
18607                    self.write_keyword("RESPECT NULLS");
18608                }
18609                None => {}
18610            }
18611        }
18612        self.write(")");
18613        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
18614        if !self.config.ignore_nulls_in_func {
18615            match f.ignore_nulls {
18616                Some(true) => {
18617                    self.write_space();
18618                    self.write_keyword("IGNORE NULLS");
18619                }
18620                Some(false) => {
18621                    self.write_space();
18622                    self.write_keyword("RESPECT NULLS");
18623                }
18624                None => {}
18625            }
18626        }
18627        Ok(())
18628    }
18629
18630    fn generate_nth_value(&mut self, f: &NthValueFunc) -> Result<()> {
18631        self.write_keyword("NTH_VALUE");
18632        self.write("(");
18633        self.generate_expression(&f.this)?;
18634        self.write(", ");
18635        self.generate_expression(&f.offset)?;
18636        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
18637        if self.config.ignore_nulls_in_func {
18638            match f.ignore_nulls {
18639                Some(true) => {
18640                    self.write_space();
18641                    self.write_keyword("IGNORE NULLS");
18642                }
18643                Some(false) => {
18644                    self.write_space();
18645                    self.write_keyword("RESPECT NULLS");
18646                }
18647                None => {}
18648            }
18649        }
18650        self.write(")");
18651        // FROM FIRST / FROM LAST (Snowflake-specific, before IGNORE/RESPECT NULLS)
18652        if matches!(
18653            self.config.dialect,
18654            Some(crate::dialects::DialectType::Snowflake)
18655        ) {
18656            match f.from_first {
18657                Some(true) => {
18658                    self.write_space();
18659                    self.write_keyword("FROM FIRST");
18660                }
18661                Some(false) => {
18662                    self.write_space();
18663                    self.write_keyword("FROM LAST");
18664                }
18665                None => {}
18666            }
18667        }
18668        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
18669        if !self.config.ignore_nulls_in_func {
18670            match f.ignore_nulls {
18671                Some(true) => {
18672                    self.write_space();
18673                    self.write_keyword("IGNORE NULLS");
18674                }
18675                Some(false) => {
18676                    self.write_space();
18677                    self.write_keyword("RESPECT NULLS");
18678                }
18679                None => {}
18680            }
18681        }
18682        Ok(())
18683    }
18684
18685    // Additional string function generators
18686
18687    fn generate_position(&mut self, f: &PositionFunc) -> Result<()> {
18688        // Standard syntax: POSITION(substr IN str)
18689        // ClickHouse prefers comma syntax with reversed arg order: POSITION(str, substr[, start])
18690        if matches!(
18691            self.config.dialect,
18692            Some(crate::dialects::DialectType::ClickHouse)
18693        ) {
18694            self.write_keyword("POSITION");
18695            self.write("(");
18696            self.generate_expression(&f.string)?;
18697            self.write(", ");
18698            self.generate_expression(&f.substring)?;
18699            if let Some(ref start) = f.start {
18700                self.write(", ");
18701                self.generate_expression(start)?;
18702            }
18703            self.write(")");
18704            return Ok(());
18705        }
18706
18707        self.write_keyword("POSITION");
18708        self.write("(");
18709        self.generate_expression(&f.substring)?;
18710        self.write_space();
18711        self.write_keyword("IN");
18712        self.write_space();
18713        self.generate_expression(&f.string)?;
18714        if let Some(ref start) = f.start {
18715            self.write(", ");
18716            self.generate_expression(start)?;
18717        }
18718        self.write(")");
18719        Ok(())
18720    }
18721
18722    // Additional math function generators
18723
18724    fn generate_rand(&mut self, f: &Rand) -> Result<()> {
18725        // Teradata RANDOM(lower, upper)
18726        if f.lower.is_some() || f.upper.is_some() {
18727            self.write_keyword("RANDOM");
18728            self.write("(");
18729            if let Some(ref lower) = f.lower {
18730                self.generate_expression(lower)?;
18731            }
18732            if let Some(ref upper) = f.upper {
18733                self.write(", ");
18734                self.generate_expression(upper)?;
18735            }
18736            self.write(")");
18737            return Ok(());
18738        }
18739        // Snowflake uses RANDOM instead of RAND, DuckDB uses RANDOM without seed
18740        let func_name = match self.config.dialect {
18741            Some(crate::dialects::DialectType::Snowflake)
18742            | Some(crate::dialects::DialectType::DuckDB) => "RANDOM",
18743            _ => "RAND",
18744        };
18745        self.write_keyword(func_name);
18746        self.write("(");
18747        // DuckDB doesn't support seeded RANDOM, so skip the seed
18748        if !matches!(
18749            self.config.dialect,
18750            Some(crate::dialects::DialectType::DuckDB)
18751        ) {
18752            if let Some(ref seed) = f.seed {
18753                self.generate_expression(seed)?;
18754            }
18755        }
18756        self.write(")");
18757        Ok(())
18758    }
18759
18760    fn generate_truncate_func(&mut self, f: &TruncateFunc) -> Result<()> {
18761        self.write_keyword("TRUNCATE");
18762        self.write("(");
18763        self.generate_expression(&f.this)?;
18764        if let Some(ref decimals) = f.decimals {
18765            self.write(", ");
18766            self.generate_expression(decimals)?;
18767        }
18768        self.write(")");
18769        Ok(())
18770    }
18771
18772    // Control flow generators
18773
18774    fn generate_decode(&mut self, f: &DecodeFunc) -> Result<()> {
18775        self.write_keyword("DECODE");
18776        self.write("(");
18777        self.generate_expression(&f.this)?;
18778        for (search, result) in &f.search_results {
18779            self.write(", ");
18780            self.generate_expression(search)?;
18781            self.write(", ");
18782            self.generate_expression(result)?;
18783        }
18784        if let Some(ref default) = f.default {
18785            self.write(", ");
18786            self.generate_expression(default)?;
18787        }
18788        self.write(")");
18789        Ok(())
18790    }
18791
18792    // Date/time function generators
18793
18794    fn generate_date_format(&mut self, name: &str, f: &DateFormatFunc) -> Result<()> {
18795        self.write_keyword(name);
18796        self.write("(");
18797        self.generate_expression(&f.this)?;
18798        self.write(", ");
18799        self.generate_expression(&f.format)?;
18800        self.write(")");
18801        Ok(())
18802    }
18803
18804    fn generate_from_unixtime(&mut self, f: &FromUnixtimeFunc) -> Result<()> {
18805        self.write_keyword("FROM_UNIXTIME");
18806        self.write("(");
18807        self.generate_expression(&f.this)?;
18808        if let Some(ref format) = f.format {
18809            self.write(", ");
18810            self.generate_expression(format)?;
18811        }
18812        self.write(")");
18813        Ok(())
18814    }
18815
18816    fn generate_unix_timestamp(&mut self, f: &UnixTimestampFunc) -> Result<()> {
18817        self.write_keyword("UNIX_TIMESTAMP");
18818        self.write("(");
18819        if let Some(ref expr) = f.this {
18820            self.generate_expression(expr)?;
18821            if let Some(ref format) = f.format {
18822                self.write(", ");
18823                self.generate_expression(format)?;
18824            }
18825        } else if matches!(
18826            self.config.dialect,
18827            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
18828        ) {
18829            // Spark/Hive: UNIX_TIMESTAMP() -> UNIX_TIMESTAMP(CURRENT_TIMESTAMP())
18830            self.write_keyword("CURRENT_TIMESTAMP");
18831            self.write("()");
18832        }
18833        self.write(")");
18834        Ok(())
18835    }
18836
18837    fn generate_make_date(&mut self, f: &MakeDateFunc) -> Result<()> {
18838        self.write_keyword("MAKE_DATE");
18839        self.write("(");
18840        self.generate_expression(&f.year)?;
18841        self.write(", ");
18842        self.generate_expression(&f.month)?;
18843        self.write(", ");
18844        self.generate_expression(&f.day)?;
18845        self.write(")");
18846        Ok(())
18847    }
18848
18849    fn generate_make_timestamp(&mut self, f: &MakeTimestampFunc) -> Result<()> {
18850        self.write_keyword("MAKE_TIMESTAMP");
18851        self.write("(");
18852        self.generate_expression(&f.year)?;
18853        self.write(", ");
18854        self.generate_expression(&f.month)?;
18855        self.write(", ");
18856        self.generate_expression(&f.day)?;
18857        self.write(", ");
18858        self.generate_expression(&f.hour)?;
18859        self.write(", ");
18860        self.generate_expression(&f.minute)?;
18861        self.write(", ");
18862        self.generate_expression(&f.second)?;
18863        if let Some(ref tz) = f.timezone {
18864            self.write(", ");
18865            self.generate_expression(tz)?;
18866        }
18867        self.write(")");
18868        Ok(())
18869    }
18870
18871    /// Extract field names from a struct expression (either Struct or Function named STRUCT with Alias args)
18872    fn extract_struct_field_names(expr: &Expression) -> Option<Vec<String>> {
18873        match expr {
18874            Expression::Struct(s) => {
18875                if s.fields.iter().all(|(name, _)| name.is_some()) {
18876                    Some(
18877                        s.fields
18878                            .iter()
18879                            .map(|(name, _)| name.as_deref().unwrap_or("").to_string())
18880                            .collect(),
18881                    )
18882                } else {
18883                    None
18884                }
18885            }
18886            Expression::Function(f) if f.name.to_uppercase() == "STRUCT" => {
18887                // Check if all args are Alias (named fields)
18888                if f.args.iter().all(|a| matches!(a, Expression::Alias(_))) {
18889                    Some(
18890                        f.args
18891                            .iter()
18892                            .filter_map(|a| {
18893                                if let Expression::Alias(alias) = a {
18894                                    Some(alias.alias.name.clone())
18895                                } else {
18896                                    None
18897                                }
18898                            })
18899                            .collect(),
18900                    )
18901                } else {
18902                    None
18903                }
18904            }
18905            _ => None,
18906        }
18907    }
18908
18909    /// Check if a struct expression has any unnamed fields
18910    fn struct_has_unnamed_fields(expr: &Expression) -> bool {
18911        match expr {
18912            Expression::Struct(s) => s.fields.iter().any(|(name, _)| name.is_none()),
18913            Expression::Function(f) if f.name.to_uppercase() == "STRUCT" => {
18914                f.args.iter().any(|a| !matches!(a, Expression::Alias(_)))
18915            }
18916            _ => false,
18917        }
18918    }
18919
18920    /// Get the field count of a struct expression
18921    fn struct_field_count(expr: &Expression) -> usize {
18922        match expr {
18923            Expression::Struct(s) => s.fields.len(),
18924            Expression::Function(f) if f.name.to_uppercase() == "STRUCT" => f.args.len(),
18925            _ => 0,
18926        }
18927    }
18928
18929    /// Apply field names to an unnamed struct expression, producing a new expression with names
18930    fn apply_struct_field_names(expr: &Expression, field_names: &[String]) -> Expression {
18931        match expr {
18932            Expression::Struct(s) => {
18933                let mut new_fields = Vec::with_capacity(s.fields.len());
18934                for (i, (name, value)) in s.fields.iter().enumerate() {
18935                    if name.is_none() && i < field_names.len() {
18936                        new_fields.push((Some(field_names[i].clone()), value.clone()));
18937                    } else {
18938                        new_fields.push((name.clone(), value.clone()));
18939                    }
18940                }
18941                Expression::Struct(Box::new(crate::expressions::Struct { fields: new_fields }))
18942            }
18943            Expression::Function(f) if f.name.to_uppercase() == "STRUCT" => {
18944                let mut new_args = Vec::with_capacity(f.args.len());
18945                for (i, arg) in f.args.iter().enumerate() {
18946                    if !matches!(arg, Expression::Alias(_)) && i < field_names.len() {
18947                        // Wrap the value in an Alias with the inherited name
18948                        new_args.push(Expression::Alias(Box::new(crate::expressions::Alias {
18949                            this: arg.clone(),
18950                            alias: crate::expressions::Identifier::new(field_names[i].clone()),
18951                            column_aliases: Vec::new(),
18952                            pre_alias_comments: Vec::new(),
18953                            trailing_comments: Vec::new(),
18954                        })));
18955                    } else {
18956                        new_args.push(arg.clone());
18957                    }
18958                }
18959                Expression::Function(Box::new(crate::expressions::Function {
18960                    name: f.name.clone(),
18961                    args: new_args,
18962                    distinct: f.distinct,
18963                    trailing_comments: f.trailing_comments.clone(),
18964                    use_bracket_syntax: f.use_bracket_syntax,
18965                    no_parens: f.no_parens,
18966                    quoted: f.quoted,
18967                }))
18968            }
18969            _ => expr.clone(),
18970        }
18971    }
18972
18973    /// Propagate struct field names from the first struct in an array to subsequent unnamed structs.
18974    /// This implements BigQuery's implicit field name inheritance for struct arrays.
18975    /// Handles both Expression::Struct and Expression::Function named "STRUCT".
18976    fn inherit_struct_field_names(expressions: &[Expression]) -> Vec<Expression> {
18977        let first = match expressions.first() {
18978            Some(e) => e,
18979            None => return expressions.to_vec(),
18980        };
18981
18982        let field_names = match Self::extract_struct_field_names(first) {
18983            Some(names) if !names.is_empty() => names,
18984            _ => return expressions.to_vec(),
18985        };
18986
18987        let mut result = Vec::with_capacity(expressions.len());
18988        for (idx, expr) in expressions.iter().enumerate() {
18989            if idx == 0 {
18990                result.push(expr.clone());
18991                continue;
18992            }
18993            // Check if this is a struct with unnamed fields that needs name propagation
18994            if Self::struct_field_count(expr) == field_names.len()
18995                && Self::struct_has_unnamed_fields(expr)
18996            {
18997                result.push(Self::apply_struct_field_names(expr, &field_names));
18998            } else {
18999                result.push(expr.clone());
19000            }
19001        }
19002        result
19003    }
19004
19005    // Array function generators
19006
19007    fn generate_array_constructor(&mut self, f: &ArrayConstructor) -> Result<()> {
19008        // Apply struct name inheritance for target dialects that need it
19009        // (DuckDB, Spark, Databricks, Hive, Snowflake, Presto, Trino)
19010        let needs_inheritance = matches!(
19011            self.config.dialect,
19012            Some(DialectType::DuckDB)
19013                | Some(DialectType::Spark)
19014                | Some(DialectType::Databricks)
19015                | Some(DialectType::Hive)
19016                | Some(DialectType::Snowflake)
19017                | Some(DialectType::Presto)
19018                | Some(DialectType::Trino)
19019        );
19020        let propagated: Vec<Expression>;
19021        let expressions = if needs_inheritance && f.expressions.len() > 1 {
19022            propagated = Self::inherit_struct_field_names(&f.expressions);
19023            &propagated
19024        } else {
19025            &f.expressions
19026        };
19027
19028        // Check if elements should be split onto multiple lines (pretty + too wide)
19029        let should_split = if self.config.pretty && !expressions.is_empty() {
19030            let mut expr_strings: Vec<String> = Vec::with_capacity(expressions.len());
19031            for expr in expressions {
19032                let mut temp_gen = Generator::with_config(self.config.clone());
19033                temp_gen.config.pretty = false;
19034                temp_gen.generate_expression(expr)?;
19035                expr_strings.push(temp_gen.output);
19036            }
19037            self.too_wide(&expr_strings)
19038        } else {
19039            false
19040        };
19041
19042        if f.bracket_notation {
19043            // For Spark/Databricks, use ARRAY(...) with parens
19044            // For Presto/Trino/PostgreSQL, use ARRAY[...] with keyword prefix
19045            // For others (DuckDB, Snowflake), use bare [...]
19046            let (open, close) = match self.config.dialect {
19047                None
19048                | Some(DialectType::Generic)
19049                | Some(DialectType::Spark)
19050                | Some(DialectType::Databricks)
19051                | Some(DialectType::Hive) => {
19052                    self.write_keyword("ARRAY");
19053                    ("(", ")")
19054                }
19055                Some(DialectType::Presto)
19056                | Some(DialectType::Trino)
19057                | Some(DialectType::PostgreSQL)
19058                | Some(DialectType::Redshift)
19059                | Some(DialectType::Materialize)
19060                | Some(DialectType::RisingWave)
19061                | Some(DialectType::CockroachDB) => {
19062                    self.write_keyword("ARRAY");
19063                    ("[", "]")
19064                }
19065                _ => ("[", "]"),
19066            };
19067            self.write(open);
19068            if should_split {
19069                self.write_newline();
19070                self.indent_level += 1;
19071                for (i, expr) in expressions.iter().enumerate() {
19072                    self.write_indent();
19073                    self.generate_expression(expr)?;
19074                    if i + 1 < expressions.len() {
19075                        self.write(",");
19076                    }
19077                    self.write_newline();
19078                }
19079                self.indent_level -= 1;
19080                self.write_indent();
19081            } else {
19082                for (i, expr) in expressions.iter().enumerate() {
19083                    if i > 0 {
19084                        self.write(", ");
19085                    }
19086                    self.generate_expression(expr)?;
19087                }
19088            }
19089            self.write(close);
19090        } else {
19091            // Use LIST keyword if that was the original syntax (DuckDB)
19092            if f.use_list_keyword {
19093                self.write_keyword("LIST");
19094            } else {
19095                self.write_keyword("ARRAY");
19096            }
19097            // For Spark/Hive, always use ARRAY(...) with parens
19098            // Also use parens for BigQuery when the array contains a subquery (ARRAY(SELECT ...))
19099            let has_subquery = expressions
19100                .iter()
19101                .any(|e| matches!(e, Expression::Select(_)));
19102            let (open, close) = if matches!(
19103                self.config.dialect,
19104                Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive)
19105            ) || (matches!(self.config.dialect, Some(DialectType::BigQuery))
19106                && has_subquery)
19107            {
19108                ("(", ")")
19109            } else {
19110                ("[", "]")
19111            };
19112            self.write(open);
19113            if should_split {
19114                self.write_newline();
19115                self.indent_level += 1;
19116                for (i, expr) in expressions.iter().enumerate() {
19117                    self.write_indent();
19118                    self.generate_expression(expr)?;
19119                    if i + 1 < expressions.len() {
19120                        self.write(",");
19121                    }
19122                    self.write_newline();
19123                }
19124                self.indent_level -= 1;
19125                self.write_indent();
19126            } else {
19127                for (i, expr) in expressions.iter().enumerate() {
19128                    if i > 0 {
19129                        self.write(", ");
19130                    }
19131                    self.generate_expression(expr)?;
19132                }
19133            }
19134            self.write(close);
19135        }
19136        Ok(())
19137    }
19138
19139    fn generate_array_sort(&mut self, f: &ArraySortFunc) -> Result<()> {
19140        self.write_keyword("ARRAY_SORT");
19141        self.write("(");
19142        self.generate_expression(&f.this)?;
19143        if let Some(ref comp) = f.comparator {
19144            self.write(", ");
19145            self.generate_expression(comp)?;
19146        }
19147        self.write(")");
19148        Ok(())
19149    }
19150
19151    fn generate_array_join(&mut self, name: &str, f: &ArrayJoinFunc) -> Result<()> {
19152        self.write_keyword(name);
19153        self.write("(");
19154        self.generate_expression(&f.this)?;
19155        self.write(", ");
19156        self.generate_expression(&f.separator)?;
19157        if let Some(ref null_rep) = f.null_replacement {
19158            self.write(", ");
19159            self.generate_expression(null_rep)?;
19160        }
19161        self.write(")");
19162        Ok(())
19163    }
19164
19165    fn generate_unnest(&mut self, f: &UnnestFunc) -> Result<()> {
19166        self.write_keyword("UNNEST");
19167        self.write("(");
19168        self.generate_expression(&f.this)?;
19169        for extra in &f.expressions {
19170            self.write(", ");
19171            self.generate_expression(extra)?;
19172        }
19173        self.write(")");
19174        if f.with_ordinality {
19175            self.write_space();
19176            if self.config.unnest_with_ordinality {
19177                // Presto/Trino: UNNEST(arr) WITH ORDINALITY [AS alias]
19178                self.write_keyword("WITH ORDINALITY");
19179            } else if f.offset_alias.is_some() {
19180                // BigQuery: UNNEST(arr) [AS col] WITH OFFSET AS pos
19181                // Alias (if any) comes BEFORE WITH OFFSET
19182                if let Some(ref alias) = f.alias {
19183                    self.write_keyword("AS");
19184                    self.write_space();
19185                    self.generate_identifier(alias)?;
19186                    self.write_space();
19187                }
19188                self.write_keyword("WITH OFFSET");
19189                if let Some(ref offset_alias) = f.offset_alias {
19190                    self.write_space();
19191                    self.write_keyword("AS");
19192                    self.write_space();
19193                    self.generate_identifier(offset_alias)?;
19194                }
19195            } else {
19196                // WITH OFFSET (BigQuery identity) - add default "AS offset" if no explicit alias
19197                self.write_keyword("WITH OFFSET");
19198                if f.alias.is_none() {
19199                    self.write(" AS offset");
19200                }
19201            }
19202        }
19203        if let Some(ref alias) = f.alias {
19204            // Add alias for: non-WITH-OFFSET cases, Presto/Trino WITH ORDINALITY, or BigQuery WITH OFFSET + alias (no offset_alias)
19205            let should_add_alias = if !f.with_ordinality {
19206                true
19207            } else if self.config.unnest_with_ordinality {
19208                // Presto/Trino: alias comes after WITH ORDINALITY
19209                true
19210            } else if f.offset_alias.is_some() {
19211                // BigQuery expansion: alias already handled above
19212                false
19213            } else {
19214                // BigQuery WITH OFFSET + alias but no offset_alias: alias comes after
19215                true
19216            };
19217            if should_add_alias {
19218                self.write_space();
19219                self.write_keyword("AS");
19220                self.write_space();
19221                self.generate_identifier(alias)?;
19222            }
19223        }
19224        Ok(())
19225    }
19226
19227    fn generate_array_filter(&mut self, f: &ArrayFilterFunc) -> Result<()> {
19228        self.write_keyword("FILTER");
19229        self.write("(");
19230        self.generate_expression(&f.this)?;
19231        self.write(", ");
19232        self.generate_expression(&f.filter)?;
19233        self.write(")");
19234        Ok(())
19235    }
19236
19237    fn generate_array_transform(&mut self, f: &ArrayTransformFunc) -> Result<()> {
19238        self.write_keyword("TRANSFORM");
19239        self.write("(");
19240        self.generate_expression(&f.this)?;
19241        self.write(", ");
19242        self.generate_expression(&f.transform)?;
19243        self.write(")");
19244        Ok(())
19245    }
19246
19247    fn generate_sequence(&mut self, name: &str, f: &SequenceFunc) -> Result<()> {
19248        self.write_keyword(name);
19249        self.write("(");
19250        self.generate_expression(&f.start)?;
19251        self.write(", ");
19252        self.generate_expression(&f.stop)?;
19253        if let Some(ref step) = f.step {
19254            self.write(", ");
19255            self.generate_expression(step)?;
19256        }
19257        self.write(")");
19258        Ok(())
19259    }
19260
19261    // Struct function generators
19262
19263    fn generate_struct_constructor(&mut self, f: &StructConstructor) -> Result<()> {
19264        self.write_keyword("STRUCT");
19265        self.write("(");
19266        for (i, (name, expr)) in f.fields.iter().enumerate() {
19267            if i > 0 {
19268                self.write(", ");
19269            }
19270            if let Some(ref id) = name {
19271                self.generate_identifier(id)?;
19272                self.write(" ");
19273                self.write_keyword("AS");
19274                self.write(" ");
19275            }
19276            self.generate_expression(expr)?;
19277        }
19278        self.write(")");
19279        Ok(())
19280    }
19281
19282    /// Convert BigQuery STRUCT function (parsed as Function with Alias args) to target dialect
19283    fn generate_struct_function_cross_dialect(&mut self, func: &Function) -> Result<()> {
19284        // Extract named/unnamed fields from function args
19285        // Args are either Alias(this=value, alias=name) for named or plain expressions for unnamed
19286        let mut names: Vec<Option<String>> = Vec::new();
19287        let mut values: Vec<&Expression> = Vec::new();
19288        let mut all_named = true;
19289
19290        for arg in &func.args {
19291            match arg {
19292                Expression::Alias(a) => {
19293                    names.push(Some(a.alias.name.clone()));
19294                    values.push(&a.this);
19295                }
19296                _ => {
19297                    names.push(None);
19298                    values.push(arg);
19299                    all_named = false;
19300                }
19301            }
19302        }
19303
19304        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
19305            // DuckDB: {'name': value, ...} for named, {'_0': value, ...} for unnamed
19306            self.write("{");
19307            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
19308                if i > 0 {
19309                    self.write(", ");
19310                }
19311                if let Some(n) = name {
19312                    self.write("'");
19313                    self.write(n);
19314                    self.write("'");
19315                } else {
19316                    self.write("'_");
19317                    self.write(&i.to_string());
19318                    self.write("'");
19319                }
19320                self.write(": ");
19321                self.generate_expression(value)?;
19322            }
19323            self.write("}");
19324            return Ok(());
19325        }
19326
19327        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
19328            // Snowflake: OBJECT_CONSTRUCT('name', value, ...)
19329            self.write_keyword("OBJECT_CONSTRUCT");
19330            self.write("(");
19331            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
19332                if i > 0 {
19333                    self.write(", ");
19334                }
19335                if let Some(n) = name {
19336                    self.write("'");
19337                    self.write(n);
19338                    self.write("'");
19339                } else {
19340                    self.write("'_");
19341                    self.write(&i.to_string());
19342                    self.write("'");
19343                }
19344                self.write(", ");
19345                self.generate_expression(value)?;
19346            }
19347            self.write(")");
19348            return Ok(());
19349        }
19350
19351        if matches!(
19352            self.config.dialect,
19353            Some(DialectType::Presto) | Some(DialectType::Trino)
19354        ) {
19355            if all_named && !names.is_empty() {
19356                // Presto/Trino: CAST(ROW(values...) AS ROW(name TYPE, ...))
19357                // Need to infer types from values
19358                self.write_keyword("CAST");
19359                self.write("(");
19360                self.write_keyword("ROW");
19361                self.write("(");
19362                for (i, value) in values.iter().enumerate() {
19363                    if i > 0 {
19364                        self.write(", ");
19365                    }
19366                    self.generate_expression(value)?;
19367                }
19368                self.write(")");
19369                self.write(" ");
19370                self.write_keyword("AS");
19371                self.write(" ");
19372                self.write_keyword("ROW");
19373                self.write("(");
19374                for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
19375                    if i > 0 {
19376                        self.write(", ");
19377                    }
19378                    if let Some(n) = name {
19379                        self.write(n);
19380                    }
19381                    self.write(" ");
19382                    let type_str = Self::infer_sql_type_for_presto(value);
19383                    self.write_keyword(&type_str);
19384                }
19385                self.write(")");
19386                self.write(")");
19387            } else {
19388                // Unnamed: ROW(values...)
19389                self.write_keyword("ROW");
19390                self.write("(");
19391                for (i, value) in values.iter().enumerate() {
19392                    if i > 0 {
19393                        self.write(", ");
19394                    }
19395                    self.generate_expression(value)?;
19396                }
19397                self.write(")");
19398            }
19399            return Ok(());
19400        }
19401
19402        // Default: ROW(values...) for other dialects
19403        self.write_keyword("ROW");
19404        self.write("(");
19405        for (i, value) in values.iter().enumerate() {
19406            if i > 0 {
19407                self.write(", ");
19408            }
19409            self.generate_expression(value)?;
19410        }
19411        self.write(")");
19412        Ok(())
19413    }
19414
19415    /// Infer SQL type name for a Presto/Trino ROW CAST from a literal expression
19416    fn infer_sql_type_for_presto(expr: &Expression) -> String {
19417        match expr {
19418            Expression::Literal(crate::expressions::Literal::String(_)) => "VARCHAR".to_string(),
19419            Expression::Literal(crate::expressions::Literal::Number(n)) => {
19420                if n.contains('.') {
19421                    "DOUBLE".to_string()
19422                } else {
19423                    "INTEGER".to_string()
19424                }
19425            }
19426            Expression::Boolean(_) => "BOOLEAN".to_string(),
19427            Expression::Literal(crate::expressions::Literal::Date(_)) => "DATE".to_string(),
19428            Expression::Literal(crate::expressions::Literal::Timestamp(_)) => {
19429                "TIMESTAMP".to_string()
19430            }
19431            Expression::Literal(crate::expressions::Literal::Datetime(_)) => {
19432                "TIMESTAMP".to_string()
19433            }
19434            Expression::Array(_) | Expression::ArrayFunc(_) => {
19435                // Try to infer element type from first element
19436                "ARRAY(VARCHAR)".to_string()
19437            }
19438            // For nested structs - generate a nested ROW type by inspecting fields
19439            Expression::Struct(_) | Expression::StructFunc(_) => "ROW".to_string(),
19440            Expression::Function(f) => {
19441                let up = f.name.to_uppercase();
19442                if up == "STRUCT" {
19443                    "ROW".to_string()
19444                } else if up == "CURRENT_DATE" {
19445                    "DATE".to_string()
19446                } else if up == "CURRENT_TIMESTAMP" || up == "NOW" {
19447                    "TIMESTAMP".to_string()
19448                } else {
19449                    "VARCHAR".to_string()
19450                }
19451            }
19452            _ => "VARCHAR".to_string(),
19453        }
19454    }
19455
19456    fn generate_struct_extract(&mut self, f: &StructExtractFunc) -> Result<()> {
19457        // DuckDB uses STRUCT_EXTRACT function syntax
19458        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
19459            self.write_keyword("STRUCT_EXTRACT");
19460            self.write("(");
19461            self.generate_expression(&f.this)?;
19462            self.write(", ");
19463            // Output field name as string literal
19464            self.write("'");
19465            self.write(&f.field.name);
19466            self.write("'");
19467            self.write(")");
19468            return Ok(());
19469        }
19470        self.generate_expression(&f.this)?;
19471        self.write(".");
19472        self.generate_identifier(&f.field)
19473    }
19474
19475    fn generate_named_struct(&mut self, f: &NamedStructFunc) -> Result<()> {
19476        self.write_keyword("NAMED_STRUCT");
19477        self.write("(");
19478        for (i, (name, value)) in f.pairs.iter().enumerate() {
19479            if i > 0 {
19480                self.write(", ");
19481            }
19482            self.generate_expression(name)?;
19483            self.write(", ");
19484            self.generate_expression(value)?;
19485        }
19486        self.write(")");
19487        Ok(())
19488    }
19489
19490    // Map function generators
19491
19492    fn generate_map_constructor(&mut self, f: &MapConstructor) -> Result<()> {
19493        if f.curly_brace_syntax {
19494            // Curly brace syntax: MAP {'a': 1, 'b': 2} or just {'a': 1, 'b': 2}
19495            if f.with_map_keyword {
19496                self.write_keyword("MAP");
19497                self.write(" ");
19498            }
19499            self.write("{");
19500            for (i, (key, val)) in f.keys.iter().zip(f.values.iter()).enumerate() {
19501                if i > 0 {
19502                    self.write(", ");
19503                }
19504                self.generate_expression(key)?;
19505                self.write(": ");
19506                self.generate_expression(val)?;
19507            }
19508            self.write("}");
19509        } else {
19510            // MAP function syntax: MAP(ARRAY[keys], ARRAY[values])
19511            self.write_keyword("MAP");
19512            self.write("(");
19513            self.write_keyword("ARRAY");
19514            self.write("[");
19515            for (i, key) in f.keys.iter().enumerate() {
19516                if i > 0 {
19517                    self.write(", ");
19518                }
19519                self.generate_expression(key)?;
19520            }
19521            self.write("], ");
19522            self.write_keyword("ARRAY");
19523            self.write("[");
19524            for (i, val) in f.values.iter().enumerate() {
19525                if i > 0 {
19526                    self.write(", ");
19527                }
19528                self.generate_expression(val)?;
19529            }
19530            self.write("])");
19531        }
19532        Ok(())
19533    }
19534
19535    fn generate_transform_func(&mut self, name: &str, f: &TransformFunc) -> Result<()> {
19536        self.write_keyword(name);
19537        self.write("(");
19538        self.generate_expression(&f.this)?;
19539        self.write(", ");
19540        self.generate_expression(&f.transform)?;
19541        self.write(")");
19542        Ok(())
19543    }
19544
19545    // JSON function generators
19546
19547    fn generate_json_extract(&mut self, name: &str, f: &JsonExtractFunc) -> Result<()> {
19548        use crate::dialects::DialectType;
19549
19550        // Check if we should use arrow syntax (-> or ->>)
19551        let use_arrow = f.arrow_syntax && self.dialect_supports_json_arrow();
19552
19553        if use_arrow {
19554            // Output arrow syntax: expr -> path or expr ->> path
19555            self.generate_expression(&f.this)?;
19556            if name == "JSON_EXTRACT_SCALAR" || name == "JSON_EXTRACT_PATH_TEXT" {
19557                self.write(" ->> ");
19558            } else {
19559                self.write(" -> ");
19560            }
19561            self.generate_expression(&f.path)?;
19562            return Ok(());
19563        }
19564
19565        // PostgreSQL uses #>> operator for JSONB path text extraction (only when hash_arrow_syntax is true)
19566        if f.hash_arrow_syntax
19567            && matches!(
19568                self.config.dialect,
19569                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
19570            )
19571        {
19572            self.generate_expression(&f.this)?;
19573            self.write(" #>> ");
19574            self.generate_expression(&f.path)?;
19575            return Ok(());
19576        }
19577
19578        // For PostgreSQL/Redshift, use JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT for extraction without arrow syntax
19579        // Redshift maps everything to JSON_EXTRACT_PATH_TEXT since it doesn't have JSON_EXTRACT_PATH
19580        let func_name = if matches!(self.config.dialect, Some(DialectType::Redshift)) {
19581            match name {
19582                "JSON_EXTRACT_SCALAR"
19583                | "JSON_EXTRACT_PATH_TEXT"
19584                | "JSON_EXTRACT"
19585                | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH_TEXT",
19586                _ => name,
19587            }
19588        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
19589            match name {
19590                "JSON_EXTRACT_SCALAR" | "JSON_EXTRACT_PATH_TEXT" => "JSON_EXTRACT_PATH_TEXT",
19591                "JSON_EXTRACT" | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH",
19592                _ => name,
19593            }
19594        } else {
19595            name
19596        };
19597
19598        self.write_keyword(func_name);
19599        self.write("(");
19600        // For Redshift, strip CAST(... AS JSON) wrapper from the expression
19601        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
19602            if let Expression::Cast(ref cast) = f.this {
19603                if matches!(cast.to, crate::expressions::DataType::Json) {
19604                    self.generate_expression(&cast.this)?;
19605                } else {
19606                    self.generate_expression(&f.this)?;
19607                }
19608            } else {
19609                self.generate_expression(&f.this)?;
19610            }
19611        } else {
19612            self.generate_expression(&f.this)?;
19613        }
19614        // For PostgreSQL/Redshift JSON_EXTRACT_PATH/JSON_EXTRACT_PATH_TEXT,
19615        // decompose JSON path into separate string arguments
19616        if matches!(
19617            self.config.dialect,
19618            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
19619        ) && (func_name == "JSON_EXTRACT_PATH" || func_name == "JSON_EXTRACT_PATH_TEXT")
19620        {
19621            if let Expression::Literal(Literal::String(ref s)) = f.path {
19622                let parts = Self::decompose_json_path(s);
19623                for part in &parts {
19624                    self.write(", '");
19625                    self.write(part);
19626                    self.write("'");
19627                }
19628            } else {
19629                self.write(", ");
19630                self.generate_expression(&f.path)?;
19631            }
19632        } else {
19633            self.write(", ");
19634            self.generate_expression(&f.path)?;
19635        }
19636
19637        // Output JSON_QUERY/JSON_VALUE options (Trino/Presto style)
19638        // These go BEFORE the closing parenthesis
19639        if let Some(ref wrapper) = f.wrapper_option {
19640            self.write_space();
19641            self.write_keyword(wrapper);
19642        }
19643        if let Some(ref quotes) = f.quotes_option {
19644            self.write_space();
19645            self.write_keyword(quotes);
19646            if f.on_scalar_string {
19647                self.write_space();
19648                self.write_keyword("ON SCALAR STRING");
19649            }
19650        }
19651        if let Some(ref on_err) = f.on_error {
19652            self.write_space();
19653            self.write_keyword(on_err);
19654        }
19655        if let Some(ref ret_type) = f.returning {
19656            self.write_space();
19657            self.write_keyword("RETURNING");
19658            self.write_space();
19659            self.generate_data_type(ret_type)?;
19660        }
19661
19662        self.write(")");
19663        Ok(())
19664    }
19665
19666    /// Check if the current dialect supports JSON arrow operators (-> and ->>)
19667    fn dialect_supports_json_arrow(&self) -> bool {
19668        use crate::dialects::DialectType;
19669        match self.config.dialect {
19670            // PostgreSQL, MySQL, DuckDB support -> and ->> operators
19671            Some(DialectType::PostgreSQL) => true,
19672            Some(DialectType::MySQL) => true,
19673            Some(DialectType::DuckDB) => true,
19674            Some(DialectType::CockroachDB) => true,
19675            Some(DialectType::StarRocks) => true,
19676            Some(DialectType::SQLite) => true,
19677            // Other dialects use function syntax
19678            _ => false,
19679        }
19680    }
19681
19682    fn generate_json_path(&mut self, name: &str, f: &JsonPathFunc) -> Result<()> {
19683        use crate::dialects::DialectType;
19684
19685        // PostgreSQL uses #> operator for JSONB path extraction
19686        if matches!(
19687            self.config.dialect,
19688            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
19689        ) && name == "JSON_EXTRACT_PATH"
19690        {
19691            self.generate_expression(&f.this)?;
19692            self.write(" #> ");
19693            if f.paths.len() == 1 {
19694                self.generate_expression(&f.paths[0])?;
19695            } else {
19696                // Multiple paths: ARRAY[path1, path2, ...]
19697                self.write_keyword("ARRAY");
19698                self.write("[");
19699                for (i, path) in f.paths.iter().enumerate() {
19700                    if i > 0 {
19701                        self.write(", ");
19702                    }
19703                    self.generate_expression(path)?;
19704                }
19705                self.write("]");
19706            }
19707            return Ok(());
19708        }
19709
19710        self.write_keyword(name);
19711        self.write("(");
19712        self.generate_expression(&f.this)?;
19713        for path in &f.paths {
19714            self.write(", ");
19715            self.generate_expression(path)?;
19716        }
19717        self.write(")");
19718        Ok(())
19719    }
19720
19721    fn generate_json_object(&mut self, f: &JsonObjectFunc) -> Result<()> {
19722        use crate::dialects::DialectType;
19723
19724        self.write_keyword("JSON_OBJECT");
19725        self.write("(");
19726        if f.star {
19727            self.write("*");
19728        } else {
19729            // BigQuery, MySQL, and SQLite use comma syntax: JSON_OBJECT('key', value)
19730            // Standard SQL uses colon syntax: JSON_OBJECT('key': value)
19731            // Also respect the json_key_value_pair_sep config
19732            let use_comma_syntax = self.config.json_key_value_pair_sep == ","
19733                || matches!(
19734                    self.config.dialect,
19735                    Some(DialectType::BigQuery)
19736                        | Some(DialectType::MySQL)
19737                        | Some(DialectType::SQLite)
19738                );
19739
19740            for (i, (key, value)) in f.pairs.iter().enumerate() {
19741                if i > 0 {
19742                    self.write(", ");
19743                }
19744                self.generate_expression(key)?;
19745                if use_comma_syntax {
19746                    self.write(", ");
19747                } else {
19748                    self.write(": ");
19749                }
19750                self.generate_expression(value)?;
19751            }
19752        }
19753        if let Some(null_handling) = f.null_handling {
19754            self.write_space();
19755            match null_handling {
19756                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
19757                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
19758            }
19759        }
19760        if f.with_unique_keys {
19761            self.write_space();
19762            self.write_keyword("WITH UNIQUE KEYS");
19763        }
19764        if let Some(ref ret_type) = f.returning_type {
19765            self.write_space();
19766            self.write_keyword("RETURNING");
19767            self.write_space();
19768            self.generate_data_type(ret_type)?;
19769            if f.format_json {
19770                self.write_space();
19771                self.write_keyword("FORMAT JSON");
19772            }
19773            if let Some(ref enc) = f.encoding {
19774                self.write_space();
19775                self.write_keyword("ENCODING");
19776                self.write_space();
19777                self.write(enc);
19778            }
19779        }
19780        self.write(")");
19781        Ok(())
19782    }
19783
19784    fn generate_json_modify(&mut self, name: &str, f: &JsonModifyFunc) -> Result<()> {
19785        self.write_keyword(name);
19786        self.write("(");
19787        self.generate_expression(&f.this)?;
19788        for (path, value) in &f.path_values {
19789            self.write(", ");
19790            self.generate_expression(path)?;
19791            self.write(", ");
19792            self.generate_expression(value)?;
19793        }
19794        self.write(")");
19795        Ok(())
19796    }
19797
19798    fn generate_json_array_agg(&mut self, f: &JsonArrayAggFunc) -> Result<()> {
19799        self.write_keyword("JSON_ARRAYAGG");
19800        self.write("(");
19801        self.generate_expression(&f.this)?;
19802        if let Some(ref order_by) = f.order_by {
19803            self.write_space();
19804            self.write_keyword("ORDER BY");
19805            self.write_space();
19806            for (i, ord) in order_by.iter().enumerate() {
19807                if i > 0 {
19808                    self.write(", ");
19809                }
19810                self.generate_ordered(ord)?;
19811            }
19812        }
19813        if let Some(null_handling) = f.null_handling {
19814            self.write_space();
19815            match null_handling {
19816                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
19817                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
19818            }
19819        }
19820        self.write(")");
19821        if let Some(ref filter) = f.filter {
19822            self.write_space();
19823            self.write_keyword("FILTER");
19824            self.write("(");
19825            self.write_keyword("WHERE");
19826            self.write_space();
19827            self.generate_expression(filter)?;
19828            self.write(")");
19829        }
19830        Ok(())
19831    }
19832
19833    fn generate_json_object_agg(&mut self, f: &JsonObjectAggFunc) -> Result<()> {
19834        self.write_keyword("JSON_OBJECTAGG");
19835        self.write("(");
19836        self.generate_expression(&f.key)?;
19837        self.write(": ");
19838        self.generate_expression(&f.value)?;
19839        if let Some(null_handling) = f.null_handling {
19840            self.write_space();
19841            match null_handling {
19842                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
19843                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
19844            }
19845        }
19846        self.write(")");
19847        if let Some(ref filter) = f.filter {
19848            self.write_space();
19849            self.write_keyword("FILTER");
19850            self.write("(");
19851            self.write_keyword("WHERE");
19852            self.write_space();
19853            self.generate_expression(filter)?;
19854            self.write(")");
19855        }
19856        Ok(())
19857    }
19858
19859    // Type casting/conversion generators
19860
19861    fn generate_convert(&mut self, f: &ConvertFunc) -> Result<()> {
19862        use crate::dialects::DialectType;
19863
19864        // Redshift: CONVERT(type, expr) -> CAST(expr AS type)
19865        if self.config.dialect == Some(DialectType::Redshift) {
19866            self.write_keyword("CAST");
19867            self.write("(");
19868            self.generate_expression(&f.this)?;
19869            self.write_space();
19870            self.write_keyword("AS");
19871            self.write_space();
19872            self.generate_data_type(&f.to)?;
19873            self.write(")");
19874            return Ok(());
19875        }
19876
19877        self.write_keyword("CONVERT");
19878        self.write("(");
19879        self.generate_data_type(&f.to)?;
19880        self.write(", ");
19881        self.generate_expression(&f.this)?;
19882        if let Some(ref style) = f.style {
19883            self.write(", ");
19884            self.generate_expression(style)?;
19885        }
19886        self.write(")");
19887        Ok(())
19888    }
19889
19890    // Additional expression generators
19891
19892    fn generate_lambda(&mut self, f: &LambdaExpr) -> Result<()> {
19893        if f.colon {
19894            // DuckDB syntax: LAMBDA x : expr
19895            self.write_keyword("LAMBDA");
19896            self.write_space();
19897            for (i, param) in f.parameters.iter().enumerate() {
19898                if i > 0 {
19899                    self.write(", ");
19900                }
19901                self.generate_identifier(param)?;
19902            }
19903            self.write(" : ");
19904        } else {
19905            // Standard syntax: x -> expr or (x, y) -> expr
19906            if f.parameters.len() == 1 {
19907                self.generate_identifier(&f.parameters[0])?;
19908            } else {
19909                self.write("(");
19910                for (i, param) in f.parameters.iter().enumerate() {
19911                    if i > 0 {
19912                        self.write(", ");
19913                    }
19914                    self.generate_identifier(param)?;
19915                }
19916                self.write(")");
19917            }
19918            self.write(" -> ");
19919        }
19920        self.generate_expression(&f.body)
19921    }
19922
19923    fn generate_named_argument(&mut self, f: &NamedArgument) -> Result<()> {
19924        self.generate_identifier(&f.name)?;
19925        match f.separator {
19926            NamedArgSeparator::DArrow => self.write(" => "),
19927            NamedArgSeparator::ColonEq => self.write(" := "),
19928            NamedArgSeparator::Eq => self.write(" = "),
19929        }
19930        self.generate_expression(&f.value)
19931    }
19932
19933    fn generate_table_argument(&mut self, f: &TableArgument) -> Result<()> {
19934        self.write_keyword(&f.prefix);
19935        self.write(" ");
19936        self.generate_expression(&f.this)
19937    }
19938
19939    fn generate_parameter(&mut self, f: &Parameter) -> Result<()> {
19940        match f.style {
19941            ParameterStyle::Question => self.write("?"),
19942            ParameterStyle::Dollar => {
19943                self.write("$");
19944                if let Some(idx) = f.index {
19945                    self.write(&idx.to_string());
19946                } else if let Some(ref name) = f.name {
19947                    // Session variable like $x or $query_id
19948                    self.write(name);
19949                }
19950            }
19951            ParameterStyle::DollarBrace => {
19952                // Template variable like ${x} or ${hiveconf:name} (Databricks, Hive)
19953                self.write("${");
19954                if let Some(ref name) = f.name {
19955                    self.write(name);
19956                }
19957                if let Some(ref expr) = f.expression {
19958                    self.write(":");
19959                    self.write(expr);
19960                }
19961                self.write("}");
19962            }
19963            ParameterStyle::Colon => {
19964                self.write(":");
19965                if let Some(idx) = f.index {
19966                    self.write(&idx.to_string());
19967                } else if let Some(ref name) = f.name {
19968                    self.write(name);
19969                }
19970            }
19971            ParameterStyle::At => {
19972                self.write("@");
19973                if let Some(ref name) = f.name {
19974                    if f.string_quoted {
19975                        self.write("'");
19976                        self.write(name);
19977                        self.write("'");
19978                    } else if f.quoted {
19979                        self.write("\"");
19980                        self.write(name);
19981                        self.write("\"");
19982                    } else {
19983                        self.write(name);
19984                    }
19985                }
19986            }
19987            ParameterStyle::DoubleAt => {
19988                self.write("@@");
19989                if let Some(ref name) = f.name {
19990                    self.write(name);
19991                }
19992            }
19993            ParameterStyle::DoubleDollar => {
19994                self.write("$$");
19995                if let Some(ref name) = f.name {
19996                    self.write(name);
19997                }
19998            }
19999            ParameterStyle::Percent => {
20000                if let Some(ref name) = f.name {
20001                    // %(name)s format
20002                    self.write("%(");
20003                    self.write(name);
20004                    self.write(")s");
20005                } else {
20006                    // %s format
20007                    self.write("%s");
20008                }
20009            }
20010            ParameterStyle::Brace => {
20011                // Spark/Databricks widget template variable: {name}
20012                // ClickHouse query parameter may include kind: {name: Type}
20013                self.write("{");
20014                if let Some(ref name) = f.name {
20015                    self.write(name);
20016                }
20017                if let Some(ref expr) = f.expression {
20018                    self.write(": ");
20019                    self.write(expr);
20020                }
20021                self.write("}");
20022            }
20023        }
20024        Ok(())
20025    }
20026
20027    fn generate_placeholder(&mut self, f: &Placeholder) -> Result<()> {
20028        self.write("?");
20029        if let Some(idx) = f.index {
20030            self.write(&idx.to_string());
20031        }
20032        Ok(())
20033    }
20034
20035    fn generate_sql_comment(&mut self, f: &SqlComment) -> Result<()> {
20036        if f.is_block {
20037            self.write("/*");
20038            self.write(&f.text);
20039            self.write("*/");
20040        } else {
20041            self.write("--");
20042            self.write(&f.text);
20043        }
20044        Ok(())
20045    }
20046
20047    // Additional predicate generators
20048
20049    fn generate_similar_to(&mut self, f: &SimilarToExpr) -> Result<()> {
20050        self.generate_expression(&f.this)?;
20051        if f.not {
20052            self.write_space();
20053            self.write_keyword("NOT");
20054        }
20055        self.write_space();
20056        self.write_keyword("SIMILAR TO");
20057        self.write_space();
20058        self.generate_expression(&f.pattern)?;
20059        if let Some(ref escape) = f.escape {
20060            self.write_space();
20061            self.write_keyword("ESCAPE");
20062            self.write_space();
20063            self.generate_expression(escape)?;
20064        }
20065        Ok(())
20066    }
20067
20068    fn generate_quantified(&mut self, name: &str, f: &QuantifiedExpr) -> Result<()> {
20069        self.generate_expression(&f.this)?;
20070        self.write_space();
20071        // Output comparison operator if present
20072        if let Some(op) = &f.op {
20073            match op {
20074                QuantifiedOp::Eq => self.write("="),
20075                QuantifiedOp::Neq => self.write("<>"),
20076                QuantifiedOp::Lt => self.write("<"),
20077                QuantifiedOp::Lte => self.write("<="),
20078                QuantifiedOp::Gt => self.write(">"),
20079                QuantifiedOp::Gte => self.write(">="),
20080            }
20081            self.write_space();
20082        }
20083        self.write_keyword(name);
20084
20085        // If the child is a Subquery, it provides its own parens — output with space
20086        if matches!(&f.subquery, Expression::Subquery(_)) {
20087            self.write_space();
20088            self.generate_expression(&f.subquery)?;
20089        } else {
20090            self.write("(");
20091
20092            let is_statement = matches!(
20093                &f.subquery,
20094                Expression::Select(_)
20095                    | Expression::Union(_)
20096                    | Expression::Intersect(_)
20097                    | Expression::Except(_)
20098            );
20099
20100            if self.config.pretty && is_statement {
20101                self.write_newline();
20102                self.indent_level += 1;
20103                self.write_indent();
20104            }
20105            self.generate_expression(&f.subquery)?;
20106            if self.config.pretty && is_statement {
20107                self.write_newline();
20108                self.indent_level -= 1;
20109                self.write_indent();
20110            }
20111            self.write(")");
20112        }
20113        Ok(())
20114    }
20115
20116    fn generate_overlaps(&mut self, f: &OverlapsExpr) -> Result<()> {
20117        // Check if this is a simple binary form (this OVERLAPS expression)
20118        if let (Some(this), Some(expr)) = (&f.this, &f.expression) {
20119            self.generate_expression(this)?;
20120            self.write_space();
20121            self.write_keyword("OVERLAPS");
20122            self.write_space();
20123            self.generate_expression(expr)?;
20124        } else if let (Some(ls), Some(le), Some(rs), Some(re)) =
20125            (&f.left_start, &f.left_end, &f.right_start, &f.right_end)
20126        {
20127            // Full ANSI form: (a, b) OVERLAPS (c, d)
20128            self.write("(");
20129            self.generate_expression(ls)?;
20130            self.write(", ");
20131            self.generate_expression(le)?;
20132            self.write(")");
20133            self.write_space();
20134            self.write_keyword("OVERLAPS");
20135            self.write_space();
20136            self.write("(");
20137            self.generate_expression(rs)?;
20138            self.write(", ");
20139            self.generate_expression(re)?;
20140            self.write(")");
20141        }
20142        Ok(())
20143    }
20144
20145    // Type conversion generators
20146
20147    fn generate_try_cast(&mut self, cast: &Cast) -> Result<()> {
20148        use crate::dialects::DialectType;
20149
20150        // SingleStore uses !:> syntax for try cast
20151        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
20152            self.generate_expression(&cast.this)?;
20153            self.write(" !:> ");
20154            self.generate_data_type(&cast.to)?;
20155            return Ok(());
20156        }
20157
20158        // Teradata uses TRYCAST (no underscore)
20159        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
20160            self.write_keyword("TRYCAST");
20161            self.write("(");
20162            self.generate_expression(&cast.this)?;
20163            self.write_space();
20164            self.write_keyword("AS");
20165            self.write_space();
20166            self.generate_data_type(&cast.to)?;
20167            self.write(")");
20168            return Ok(());
20169        }
20170
20171        // Dialects without TRY_CAST: generate as regular CAST
20172        let keyword = if matches!(
20173            self.config.dialect,
20174            Some(DialectType::Hive)
20175                | Some(DialectType::MySQL)
20176                | Some(DialectType::SQLite)
20177                | Some(DialectType::Oracle)
20178                | Some(DialectType::ClickHouse)
20179                | Some(DialectType::Redshift)
20180                | Some(DialectType::PostgreSQL)
20181                | Some(DialectType::StarRocks)
20182                | Some(DialectType::Doris)
20183        ) {
20184            "CAST"
20185        } else {
20186            "TRY_CAST"
20187        };
20188
20189        self.write_keyword(keyword);
20190        self.write("(");
20191        self.generate_expression(&cast.this)?;
20192        self.write_space();
20193        self.write_keyword("AS");
20194        self.write_space();
20195        self.generate_data_type(&cast.to)?;
20196
20197        // Output FORMAT clause if present
20198        if let Some(format) = &cast.format {
20199            self.write_space();
20200            self.write_keyword("FORMAT");
20201            self.write_space();
20202            self.generate_expression(format)?;
20203        }
20204
20205        self.write(")");
20206        Ok(())
20207    }
20208
20209    fn generate_safe_cast(&mut self, cast: &Cast) -> Result<()> {
20210        self.write_keyword("SAFE_CAST");
20211        self.write("(");
20212        self.generate_expression(&cast.this)?;
20213        self.write_space();
20214        self.write_keyword("AS");
20215        self.write_space();
20216        self.generate_data_type(&cast.to)?;
20217
20218        // Output FORMAT clause if present
20219        if let Some(format) = &cast.format {
20220            self.write_space();
20221            self.write_keyword("FORMAT");
20222            self.write_space();
20223            self.generate_expression(format)?;
20224        }
20225
20226        self.write(")");
20227        Ok(())
20228    }
20229
20230    // Array/struct/map access generators
20231
20232    fn generate_subscript(&mut self, s: &Subscript) -> Result<()> {
20233        self.generate_expression(&s.this)?;
20234        self.write("[");
20235        self.generate_expression(&s.index)?;
20236        self.write("]");
20237        Ok(())
20238    }
20239
20240    fn generate_dot_access(&mut self, d: &DotAccess) -> Result<()> {
20241        self.generate_expression(&d.this)?;
20242        // Snowflake uses : (colon) for first-level struct/object field access on CAST/column expressions
20243        // e.g., CAST(col AS OBJECT(fld1 OBJECT(fld2 INT))):fld1.fld2
20244        let use_colon = matches!(self.config.dialect, Some(DialectType::Snowflake))
20245            && matches!(
20246                &d.this,
20247                Expression::Cast(_) | Expression::SafeCast(_) | Expression::TryCast(_)
20248            );
20249        if use_colon {
20250            self.write(":");
20251        } else {
20252            self.write(".");
20253        }
20254        self.generate_identifier(&d.field)
20255    }
20256
20257    fn generate_method_call(&mut self, m: &MethodCall) -> Result<()> {
20258        self.generate_expression(&m.this)?;
20259        self.write(".");
20260        // Method names after a dot should not be quoted based on reserved keywords
20261        // Only quote if explicitly marked as quoted in the AST
20262        if m.method.quoted {
20263            let q = self.config.identifier_quote;
20264            self.write(&format!("{}{}{}", q, m.method.name, q));
20265        } else {
20266            self.write(&m.method.name);
20267        }
20268        self.write("(");
20269        for (i, arg) in m.args.iter().enumerate() {
20270            if i > 0 {
20271                self.write(", ");
20272            }
20273            self.generate_expression(arg)?;
20274        }
20275        self.write(")");
20276        Ok(())
20277    }
20278
20279    fn generate_array_slice(&mut self, s: &ArraySlice) -> Result<()> {
20280        // Check if we need to wrap the inner expression in parentheses
20281        // JSON arrow expressions have lower precedence than array subscript
20282        let needs_parens = matches!(
20283            &s.this,
20284            Expression::JsonExtract(f) if f.arrow_syntax
20285        ) || matches!(
20286            &s.this,
20287            Expression::JsonExtractScalar(f) if f.arrow_syntax
20288        );
20289
20290        if needs_parens {
20291            self.write("(");
20292        }
20293        self.generate_expression(&s.this)?;
20294        if needs_parens {
20295            self.write(")");
20296        }
20297        self.write("[");
20298        if let Some(start) = &s.start {
20299            self.generate_expression(start)?;
20300        }
20301        self.write(":");
20302        if let Some(end) = &s.end {
20303            self.generate_expression(end)?;
20304        }
20305        self.write("]");
20306        Ok(())
20307    }
20308
20309    fn generate_binary_op(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
20310        // Generate left expression, but skip trailing comments if they're already in left_comments
20311        // to avoid duplication (comments are captured as both expr.trailing_comments
20312        // and BinaryOp.left_comments during parsing)
20313        match &op.left {
20314            Expression::Column(col) => {
20315                // Generate column with trailing comments but skip them if they're
20316                // already captured in BinaryOp.left_comments to avoid duplication
20317                if let Some(table) = &col.table {
20318                    self.generate_identifier(table)?;
20319                    self.write(".");
20320                }
20321                self.generate_identifier(&col.name)?;
20322                // Oracle-style join marker (+)
20323                if col.join_mark && self.config.supports_column_join_marks {
20324                    self.write(" (+)");
20325                }
20326                // Output column trailing comments if they're not already in left_comments
20327                if op.left_comments.is_empty() {
20328                    for comment in &col.trailing_comments {
20329                        self.write_space();
20330                        self.write_formatted_comment(comment);
20331                    }
20332                }
20333            }
20334            Expression::Add(inner_op)
20335            | Expression::Sub(inner_op)
20336            | Expression::Mul(inner_op)
20337            | Expression::Div(inner_op)
20338            | Expression::Concat(inner_op) => {
20339                // Generate binary op without its trailing comments
20340                self.generate_binary_op_no_trailing(inner_op, match &op.left {
20341                    Expression::Add(_) => "+",
20342                    Expression::Sub(_) => "-",
20343                    Expression::Mul(_) => "*",
20344                    Expression::Div(_) => "/",
20345                    Expression::Concat(_) => "||",
20346                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
20347                })?;
20348            }
20349            _ => {
20350                self.generate_expression(&op.left)?;
20351            }
20352        }
20353        // Output comments after left operand
20354        for comment in &op.left_comments {
20355            self.write_space();
20356            self.write_formatted_comment(comment);
20357        }
20358        if self.config.pretty
20359            && matches!(self.config.dialect, Some(DialectType::Snowflake))
20360            && (operator == "AND" || operator == "OR")
20361        {
20362            self.write_newline();
20363            self.write_indent();
20364            self.write_keyword(operator);
20365        } else {
20366            self.write_space();
20367            if operator.chars().all(|c| c.is_alphabetic()) {
20368                self.write_keyword(operator);
20369            } else {
20370                self.write(operator);
20371            }
20372        }
20373        // Output comments after operator (before right operand)
20374        for comment in &op.operator_comments {
20375            self.write_space();
20376            self.write_formatted_comment(comment);
20377        }
20378        self.write_space();
20379        self.generate_expression(&op.right)?;
20380        // Output trailing comments after right operand
20381        for comment in &op.trailing_comments {
20382            self.write_space();
20383            self.write_formatted_comment(comment);
20384        }
20385        Ok(())
20386    }
20387
20388    /// Generate LIKE/ILIKE operation with optional ESCAPE clause
20389    fn generate_like_op(&mut self, op: &LikeOp, operator: &str) -> Result<()> {
20390        self.generate_expression(&op.left)?;
20391        self.write_space();
20392        // Drill backtick-quotes ILIKE
20393        if operator == "ILIKE" && matches!(self.config.dialect, Some(DialectType::Drill)) {
20394            self.write("`ILIKE`");
20395        } else {
20396            self.write_keyword(operator);
20397        }
20398        if let Some(quantifier) = &op.quantifier {
20399            self.write_space();
20400            self.write_keyword(quantifier);
20401        }
20402        self.write_space();
20403        self.generate_expression(&op.right)?;
20404        if let Some(escape) = &op.escape {
20405            self.write_space();
20406            self.write_keyword("ESCAPE");
20407            self.write_space();
20408            self.generate_expression(escape)?;
20409        }
20410        Ok(())
20411    }
20412
20413    /// Generate null-safe equality
20414    /// MySQL uses <=>, other dialects use IS NOT DISTINCT FROM
20415    fn generate_null_safe_eq(&mut self, op: &BinaryOp) -> Result<()> {
20416        use crate::dialects::DialectType;
20417        self.generate_expression(&op.left)?;
20418        self.write_space();
20419        if matches!(self.config.dialect, Some(DialectType::MySQL)) {
20420            self.write("<=>");
20421        } else {
20422            self.write_keyword("IS NOT DISTINCT FROM");
20423        }
20424        self.write_space();
20425        self.generate_expression(&op.right)?;
20426        Ok(())
20427    }
20428
20429    /// Generate IS DISTINCT FROM (null-safe inequality)
20430    fn generate_null_safe_neq(&mut self, op: &BinaryOp) -> Result<()> {
20431        self.generate_expression(&op.left)?;
20432        self.write_space();
20433        self.write_keyword("IS DISTINCT FROM");
20434        self.write_space();
20435        self.generate_expression(&op.right)?;
20436        Ok(())
20437    }
20438
20439    /// Generate binary op without trailing comments (used when nested inside another binary op)
20440    fn generate_binary_op_no_trailing(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
20441        // Generate left expression, but skip trailing comments
20442        match &op.left {
20443            Expression::Column(col) => {
20444                if let Some(table) = &col.table {
20445                    self.generate_identifier(table)?;
20446                    self.write(".");
20447                }
20448                self.generate_identifier(&col.name)?;
20449                // Oracle-style join marker (+)
20450                if col.join_mark && self.config.supports_column_join_marks {
20451                    self.write(" (+)");
20452                }
20453            }
20454            Expression::Add(inner_op)
20455            | Expression::Sub(inner_op)
20456            | Expression::Mul(inner_op)
20457            | Expression::Div(inner_op)
20458            | Expression::Concat(inner_op) => {
20459                self.generate_binary_op_no_trailing(inner_op, match &op.left {
20460                    Expression::Add(_) => "+",
20461                    Expression::Sub(_) => "-",
20462                    Expression::Mul(_) => "*",
20463                    Expression::Div(_) => "/",
20464                    Expression::Concat(_) => "||",
20465                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
20466                })?;
20467            }
20468            _ => {
20469                self.generate_expression(&op.left)?;
20470            }
20471        }
20472        // Output left_comments
20473        for comment in &op.left_comments {
20474            self.write_space();
20475            self.write_formatted_comment(comment);
20476        }
20477        self.write_space();
20478        if operator.chars().all(|c| c.is_alphabetic()) {
20479            self.write_keyword(operator);
20480        } else {
20481            self.write(operator);
20482        }
20483        // Output operator_comments
20484        for comment in &op.operator_comments {
20485            self.write_space();
20486            self.write_formatted_comment(comment);
20487        }
20488        self.write_space();
20489        // Generate right expression, but skip trailing comments if it's a Column
20490        // (the parent's left_comments will output them)
20491        match &op.right {
20492            Expression::Column(col) => {
20493                if let Some(table) = &col.table {
20494                    self.generate_identifier(table)?;
20495                    self.write(".");
20496                }
20497                self.generate_identifier(&col.name)?;
20498                // Oracle-style join marker (+)
20499                if col.join_mark && self.config.supports_column_join_marks {
20500                    self.write(" (+)");
20501                }
20502            }
20503            _ => {
20504                self.generate_expression(&op.right)?;
20505            }
20506        }
20507        // Skip trailing_comments - parent will handle them via its left_comments
20508        Ok(())
20509    }
20510
20511    fn generate_unary_op(&mut self, op: &UnaryOp, operator: &str) -> Result<()> {
20512        if operator.chars().all(|c| c.is_alphabetic()) {
20513            self.write_keyword(operator);
20514            self.write_space();
20515        } else {
20516            self.write(operator);
20517            // Add space between consecutive unary operators (e.g., "- -5" not "--5")
20518            if matches!(&op.this, Expression::Neg(_) | Expression::BitwiseNot(_)) {
20519                self.write_space();
20520            }
20521        }
20522        self.generate_expression(&op.this)
20523    }
20524
20525    fn generate_in(&mut self, in_expr: &In) -> Result<()> {
20526        // Generic mode supports two styles for negated IN:
20527        // - Prefix: NOT a IN (...)
20528        // - Infix:  a NOT IN (...)
20529        let is_generic =
20530            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
20531        let use_prefix_not =
20532            in_expr.not && is_generic && self.config.not_in_style == NotInStyle::Prefix;
20533        if use_prefix_not {
20534            self.write_keyword("NOT");
20535            self.write_space();
20536        }
20537        self.generate_expression(&in_expr.this)?;
20538        if in_expr.global {
20539            self.write_space();
20540            self.write_keyword("GLOBAL");
20541        }
20542        if in_expr.not && !use_prefix_not {
20543            self.write_space();
20544            self.write_keyword("NOT");
20545        }
20546        self.write_space();
20547        self.write_keyword("IN");
20548
20549        // BigQuery: IN UNNEST(expr)
20550        if let Some(unnest_expr) = &in_expr.unnest {
20551            self.write_space();
20552            self.write_keyword("UNNEST");
20553            self.write("(");
20554            self.generate_expression(unnest_expr)?;
20555            self.write(")");
20556            return Ok(());
20557        }
20558
20559        if let Some(query) = &in_expr.query {
20560            // Check if this is a bare identifier (PIVOT FOR foo IN y_enum)
20561            // vs a subquery (col IN (SELECT ...))
20562            let is_bare = in_expr.expressions.is_empty()
20563                && !matches!(
20564                    query,
20565                    Expression::Select(_)
20566                        | Expression::Union(_)
20567                        | Expression::Intersect(_)
20568                        | Expression::Except(_)
20569                        | Expression::Subquery(_)
20570                );
20571            if is_bare {
20572                // Bare identifier: no parentheses
20573                self.write_space();
20574                self.generate_expression(query)?;
20575            } else {
20576                // Subquery: with parentheses
20577                self.write(" (");
20578                let is_statement = matches!(
20579                    query,
20580                    Expression::Select(_)
20581                        | Expression::Union(_)
20582                        | Expression::Intersect(_)
20583                        | Expression::Except(_)
20584                        | Expression::Subquery(_)
20585                );
20586                if self.config.pretty && is_statement {
20587                    self.write_newline();
20588                    self.indent_level += 1;
20589                    self.write_indent();
20590                }
20591                self.generate_expression(query)?;
20592                if self.config.pretty && is_statement {
20593                    self.write_newline();
20594                    self.indent_level -= 1;
20595                    self.write_indent();
20596                }
20597                self.write(")");
20598            }
20599        } else {
20600            // DuckDB: IN without parentheses for single expression that is NOT a literal
20601            // (array/list membership like 'red' IN tbl.flags)
20602            // ClickHouse: IN without parentheses for single non-array expressions
20603            let is_duckdb = matches!(
20604                self.config.dialect,
20605                Some(crate::dialects::DialectType::DuckDB)
20606            );
20607            let is_clickhouse = matches!(
20608                self.config.dialect,
20609                Some(crate::dialects::DialectType::ClickHouse)
20610            );
20611            let single_expr = in_expr.expressions.len() == 1;
20612            if is_clickhouse && single_expr {
20613                if let Expression::Array(arr) = &in_expr.expressions[0] {
20614                    // ClickHouse: x IN [1, 2] -> x IN (1, 2)
20615                    self.write(" (");
20616                    for (i, expr) in arr.expressions.iter().enumerate() {
20617                        if i > 0 {
20618                            self.write(", ");
20619                        }
20620                        self.generate_expression(expr)?;
20621                    }
20622                    self.write(")");
20623                } else {
20624                    self.write_space();
20625                    self.generate_expression(&in_expr.expressions[0])?;
20626                }
20627            } else {
20628                let is_bare_ref = single_expr
20629                    && matches!(
20630                        &in_expr.expressions[0],
20631                        Expression::Column(_) | Expression::Identifier(_) | Expression::Dot(_)
20632                    );
20633                if (is_duckdb && is_bare_ref) || (in_expr.is_field && single_expr) {
20634                    // Bare field reference (no parens in source): IN identifier
20635                    // Also DuckDB: IN without parentheses for array/list membership
20636                    self.write_space();
20637                    self.generate_expression(&in_expr.expressions[0])?;
20638                } else {
20639                    // Standard IN (list)
20640                    self.write(" (");
20641                    for (i, expr) in in_expr.expressions.iter().enumerate() {
20642                        if i > 0 {
20643                            self.write(", ");
20644                        }
20645                        self.generate_expression(expr)?;
20646                    }
20647                    self.write(")");
20648                }
20649            }
20650        }
20651
20652        Ok(())
20653    }
20654
20655    fn generate_between(&mut self, between: &Between) -> Result<()> {
20656        // Generic mode: normalize NOT BETWEEN to prefix form: NOT a BETWEEN b AND c
20657        let use_prefix_not = between.not
20658            && (self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic));
20659        if use_prefix_not {
20660            self.write_keyword("NOT");
20661            self.write_space();
20662        }
20663        self.generate_expression(&between.this)?;
20664        if between.not && !use_prefix_not {
20665            self.write_space();
20666            self.write_keyword("NOT");
20667        }
20668        self.write_space();
20669        self.write_keyword("BETWEEN");
20670        // Emit SYMMETRIC/ASYMMETRIC if present
20671        if let Some(sym) = between.symmetric {
20672            if sym {
20673                self.write(" SYMMETRIC");
20674            } else {
20675                self.write(" ASYMMETRIC");
20676            }
20677        }
20678        self.write_space();
20679        self.generate_expression(&between.low)?;
20680        self.write_space();
20681        self.write_keyword("AND");
20682        self.write_space();
20683        self.generate_expression(&between.high)
20684    }
20685
20686    fn generate_is_null(&mut self, is_null: &IsNull) -> Result<()> {
20687        // Generic mode: normalize IS NOT NULL to prefix form: NOT x IS NULL
20688        let use_prefix_not = is_null.not
20689            && (self.config.dialect.is_none()
20690                || self.config.dialect == Some(DialectType::Generic)
20691                || is_null.postfix_form);
20692        if use_prefix_not {
20693            // NOT x IS NULL (generic normalization and NOTNULL postfix form)
20694            self.write_keyword("NOT");
20695            self.write_space();
20696            self.generate_expression(&is_null.this)?;
20697            self.write_space();
20698            self.write_keyword("IS");
20699            self.write_space();
20700            self.write_keyword("NULL");
20701        } else {
20702            self.generate_expression(&is_null.this)?;
20703            self.write_space();
20704            self.write_keyword("IS");
20705            if is_null.not {
20706                self.write_space();
20707                self.write_keyword("NOT");
20708            }
20709            self.write_space();
20710            self.write_keyword("NULL");
20711        }
20712        Ok(())
20713    }
20714
20715    fn generate_is_true(&mut self, is_true: &IsTrueFalse) -> Result<()> {
20716        self.generate_expression(&is_true.this)?;
20717        self.write_space();
20718        self.write_keyword("IS");
20719        if is_true.not {
20720            self.write_space();
20721            self.write_keyword("NOT");
20722        }
20723        self.write_space();
20724        self.write_keyword("TRUE");
20725        Ok(())
20726    }
20727
20728    fn generate_is_false(&mut self, is_false: &IsTrueFalse) -> Result<()> {
20729        self.generate_expression(&is_false.this)?;
20730        self.write_space();
20731        self.write_keyword("IS");
20732        if is_false.not {
20733            self.write_space();
20734            self.write_keyword("NOT");
20735        }
20736        self.write_space();
20737        self.write_keyword("FALSE");
20738        Ok(())
20739    }
20740
20741    fn generate_is_json(&mut self, is_json: &IsJson) -> Result<()> {
20742        self.generate_expression(&is_json.this)?;
20743        self.write_space();
20744        self.write_keyword("IS");
20745        if is_json.negated {
20746            self.write_space();
20747            self.write_keyword("NOT");
20748        }
20749        self.write_space();
20750        self.write_keyword("JSON");
20751
20752        // Output JSON type if specified (VALUE, SCALAR, OBJECT, ARRAY)
20753        if let Some(ref json_type) = is_json.json_type {
20754            self.write_space();
20755            self.write_keyword(json_type);
20756        }
20757
20758        // Output key uniqueness constraint if specified
20759        match &is_json.unique_keys {
20760            Some(JsonUniqueKeys::With) => {
20761                self.write_space();
20762                self.write_keyword("WITH UNIQUE KEYS");
20763            }
20764            Some(JsonUniqueKeys::Without) => {
20765                self.write_space();
20766                self.write_keyword("WITHOUT UNIQUE KEYS");
20767            }
20768            Some(JsonUniqueKeys::Shorthand) => {
20769                self.write_space();
20770                self.write_keyword("UNIQUE KEYS");
20771            }
20772            None => {}
20773        }
20774
20775        Ok(())
20776    }
20777
20778    fn generate_is(&mut self, is_expr: &BinaryOp) -> Result<()> {
20779        self.generate_expression(&is_expr.left)?;
20780        self.write_space();
20781        self.write_keyword("IS");
20782        self.write_space();
20783        self.generate_expression(&is_expr.right)
20784    }
20785
20786    fn generate_exists(&mut self, exists: &Exists) -> Result<()> {
20787        if exists.not {
20788            self.write_keyword("NOT");
20789            self.write_space();
20790        }
20791        self.write_keyword("EXISTS");
20792        self.write("(");
20793        let is_statement = matches!(
20794            &exists.this,
20795            Expression::Select(_)
20796                | Expression::Union(_)
20797                | Expression::Intersect(_)
20798                | Expression::Except(_)
20799        );
20800        if self.config.pretty && is_statement {
20801            self.write_newline();
20802            self.indent_level += 1;
20803            self.write_indent();
20804            self.generate_expression(&exists.this)?;
20805            self.write_newline();
20806            self.indent_level -= 1;
20807            self.write_indent();
20808            self.write(")");
20809        } else {
20810            self.generate_expression(&exists.this)?;
20811            self.write(")");
20812        }
20813        Ok(())
20814    }
20815
20816    fn generate_member_of(&mut self, op: &BinaryOp) -> Result<()> {
20817        self.generate_expression(&op.left)?;
20818        self.write_space();
20819        self.write_keyword("MEMBER OF");
20820        self.write("(");
20821        self.generate_expression(&op.right)?;
20822        self.write(")");
20823        Ok(())
20824    }
20825
20826    fn generate_subquery(&mut self, subquery: &Subquery) -> Result<()> {
20827        if subquery.lateral {
20828            self.write_keyword("LATERAL");
20829            self.write_space();
20830        }
20831
20832        // If the inner expression is a Paren wrapping a statement, don't add extra parentheses
20833        // This handles cases like ((SELECT 1)) LIMIT 1 where we wrap Paren in Subquery
20834        // to carry the LIMIT modifier without adding more parens
20835        let skip_outer_parens = if let Expression::Paren(ref p) = &subquery.this {
20836            matches!(
20837                &p.this,
20838                Expression::Select(_)
20839                    | Expression::Union(_)
20840                    | Expression::Intersect(_)
20841                    | Expression::Except(_)
20842                    | Expression::Subquery(_)
20843            )
20844        } else {
20845            false
20846        };
20847
20848        // Check if inner expression is a statement for pretty formatting
20849        let is_statement = matches!(
20850            &subquery.this,
20851            Expression::Select(_)
20852                | Expression::Union(_)
20853                | Expression::Intersect(_)
20854                | Expression::Except(_)
20855                | Expression::Merge(_)
20856        );
20857
20858        if !skip_outer_parens {
20859            self.write("(");
20860            if self.config.pretty && is_statement {
20861                self.write_newline();
20862                self.indent_level += 1;
20863                self.write_indent();
20864            }
20865        }
20866        self.generate_expression(&subquery.this)?;
20867
20868        // Generate ORDER BY, LIMIT, OFFSET based on modifiers_inside flag
20869        if subquery.modifiers_inside {
20870            // Generate modifiers INSIDE the parentheses: (SELECT ... LIMIT 1)
20871            if let Some(order_by) = &subquery.order_by {
20872                self.write_space();
20873                self.write_keyword("ORDER BY");
20874                self.write_space();
20875                for (i, ord) in order_by.expressions.iter().enumerate() {
20876                    if i > 0 {
20877                        self.write(", ");
20878                    }
20879                    self.generate_ordered(ord)?;
20880                }
20881            }
20882
20883            if let Some(limit) = &subquery.limit {
20884                self.write_space();
20885                self.write_keyword("LIMIT");
20886                self.write_space();
20887                self.generate_expression(&limit.this)?;
20888                if limit.percent {
20889                    self.write_space();
20890                    self.write_keyword("PERCENT");
20891                }
20892            }
20893
20894            if let Some(offset) = &subquery.offset {
20895                self.write_space();
20896                self.write_keyword("OFFSET");
20897                self.write_space();
20898                self.generate_expression(&offset.this)?;
20899            }
20900        }
20901
20902        if !skip_outer_parens {
20903            if self.config.pretty && is_statement {
20904                self.write_newline();
20905                self.indent_level -= 1;
20906                self.write_indent();
20907            }
20908            self.write(")");
20909        }
20910
20911        // Generate modifiers OUTSIDE the parentheses: (SELECT ...) LIMIT 1
20912        if !subquery.modifiers_inside {
20913            if let Some(order_by) = &subquery.order_by {
20914                self.write_space();
20915                self.write_keyword("ORDER BY");
20916                self.write_space();
20917                for (i, ord) in order_by.expressions.iter().enumerate() {
20918                    if i > 0 {
20919                        self.write(", ");
20920                    }
20921                    self.generate_ordered(ord)?;
20922                }
20923            }
20924
20925            if let Some(limit) = &subquery.limit {
20926                self.write_space();
20927                self.write_keyword("LIMIT");
20928                self.write_space();
20929                self.generate_expression(&limit.this)?;
20930                if limit.percent {
20931                    self.write_space();
20932                    self.write_keyword("PERCENT");
20933                }
20934            }
20935
20936            if let Some(offset) = &subquery.offset {
20937                self.write_space();
20938                self.write_keyword("OFFSET");
20939                self.write_space();
20940                self.generate_expression(&offset.this)?;
20941            }
20942
20943            // Generate DISTRIBUTE BY (Hive/Spark)
20944            if let Some(distribute_by) = &subquery.distribute_by {
20945                self.write_space();
20946                self.write_keyword("DISTRIBUTE BY");
20947                self.write_space();
20948                for (i, expr) in distribute_by.expressions.iter().enumerate() {
20949                    if i > 0 {
20950                        self.write(", ");
20951                    }
20952                    self.generate_expression(expr)?;
20953                }
20954            }
20955
20956            // Generate SORT BY (Hive/Spark)
20957            if let Some(sort_by) = &subquery.sort_by {
20958                self.write_space();
20959                self.write_keyword("SORT BY");
20960                self.write_space();
20961                for (i, ord) in sort_by.expressions.iter().enumerate() {
20962                    if i > 0 {
20963                        self.write(", ");
20964                    }
20965                    self.generate_ordered(ord)?;
20966                }
20967            }
20968
20969            // Generate CLUSTER BY (Hive/Spark)
20970            if let Some(cluster_by) = &subquery.cluster_by {
20971                self.write_space();
20972                self.write_keyword("CLUSTER BY");
20973                self.write_space();
20974                for (i, ord) in cluster_by.expressions.iter().enumerate() {
20975                    if i > 0 {
20976                        self.write(", ");
20977                    }
20978                    self.generate_ordered(ord)?;
20979                }
20980            }
20981        }
20982
20983        if let Some(alias) = &subquery.alias {
20984            self.write_space();
20985            // Oracle doesn't use AS for subquery aliases
20986            let skip_as = matches!(
20987                self.config.dialect,
20988                Some(crate::dialects::DialectType::Oracle)
20989            );
20990            if !skip_as {
20991                self.write_keyword("AS");
20992                self.write_space();
20993            }
20994            self.generate_identifier(alias)?;
20995            if !subquery.column_aliases.is_empty() {
20996                self.write("(");
20997                for (i, col) in subquery.column_aliases.iter().enumerate() {
20998                    if i > 0 {
20999                        self.write(", ");
21000                    }
21001                    self.generate_identifier(col)?;
21002                }
21003                self.write(")");
21004            }
21005        }
21006        // Output trailing comments
21007        for comment in &subquery.trailing_comments {
21008            self.write(" ");
21009            self.write_formatted_comment(comment);
21010        }
21011        Ok(())
21012    }
21013
21014    fn generate_pivot(&mut self, pivot: &Pivot) -> Result<()> {
21015        // Generate WITH clause if present
21016        if let Some(ref with) = pivot.with {
21017            self.generate_with(with)?;
21018            self.write_space();
21019        }
21020
21021        let direction = if pivot.unpivot { "UNPIVOT" } else { "PIVOT" };
21022
21023        // Check for Redshift UNPIVOT in FROM clause:
21024        // UNPIVOT expr [AS val AT attr]
21025        // This is when unpivot=true, expressions is empty, fields is empty, and this is not Null
21026        let is_redshift_unpivot = pivot.unpivot
21027            && pivot.expressions.is_empty()
21028            && pivot.fields.is_empty()
21029            && pivot.using.is_empty()
21030            && pivot.into.is_none()
21031            && !matches!(&pivot.this, Expression::Null(_));
21032
21033        if is_redshift_unpivot {
21034            // Redshift UNPIVOT: UNPIVOT expr [AS alias]
21035            self.write_keyword("UNPIVOT");
21036            self.write_space();
21037            self.generate_expression(&pivot.this)?;
21038            // Alias - for Redshift it can be "val AT attr" format
21039            if let Some(alias) = &pivot.alias {
21040                self.write_space();
21041                self.write_keyword("AS");
21042                self.write_space();
21043                // The alias might contain " AT " for the attr part
21044                self.write(&alias.name);
21045            }
21046            return Ok(());
21047        }
21048
21049        // Check if this is a DuckDB simplified pivot (has `using` or `into`, or no `fields`)
21050        let is_simplified = !pivot.using.is_empty()
21051            || pivot.into.is_some()
21052            || (pivot.fields.is_empty()
21053                && !pivot.expressions.is_empty()
21054                && !matches!(&pivot.this, Expression::Null(_)));
21055
21056        if is_simplified {
21057            // DuckDB simplified syntax:
21058            //   PIVOT table ON cols [IN (...)] USING agg [AS alias], ... [GROUP BY ...]
21059            //   UNPIVOT table ON cols INTO NAME col VALUE col
21060            self.write_keyword(direction);
21061            self.write_space();
21062            self.generate_expression(&pivot.this)?;
21063
21064            if !pivot.expressions.is_empty() {
21065                self.write_space();
21066                self.write_keyword("ON");
21067                self.write_space();
21068                for (i, expr) in pivot.expressions.iter().enumerate() {
21069                    if i > 0 {
21070                        self.write(", ");
21071                    }
21072                    self.generate_expression(expr)?;
21073                }
21074            }
21075
21076            // INTO (for UNPIVOT)
21077            if let Some(into) = &pivot.into {
21078                self.write_space();
21079                self.write_keyword("INTO");
21080                self.write_space();
21081                self.generate_expression(into)?;
21082            }
21083
21084            // USING (for PIVOT)
21085            if !pivot.using.is_empty() {
21086                self.write_space();
21087                self.write_keyword("USING");
21088                self.write_space();
21089                for (i, expr) in pivot.using.iter().enumerate() {
21090                    if i > 0 {
21091                        self.write(", ");
21092                    }
21093                    self.generate_expression(expr)?;
21094                }
21095            }
21096
21097            // GROUP BY
21098            if let Some(group) = &pivot.group {
21099                self.write_space();
21100                self.generate_expression(group)?;
21101            }
21102        } else {
21103            // Standard syntax:
21104            //   table PIVOT(agg [AS alias], ... FOR col IN (val [AS alias], ...) [GROUP BY ...])
21105            //   table UNPIVOT(value_col FOR name_col IN (col1, col2, ...))
21106            // Only output the table expression if it's not a Null (null is used when PIVOT comes after JOIN ON)
21107            if !matches!(&pivot.this, Expression::Null(_)) {
21108                self.generate_expression(&pivot.this)?;
21109                self.write_space();
21110            }
21111            self.write_keyword(direction);
21112            self.write("(");
21113
21114            // Aggregation expressions
21115            for (i, expr) in pivot.expressions.iter().enumerate() {
21116                if i > 0 {
21117                    self.write(", ");
21118                }
21119                self.generate_expression(expr)?;
21120            }
21121
21122            // FOR...IN fields
21123            if !pivot.fields.is_empty() {
21124                if !pivot.expressions.is_empty() {
21125                    self.write_space();
21126                }
21127                self.write_keyword("FOR");
21128                self.write_space();
21129                for (i, field) in pivot.fields.iter().enumerate() {
21130                    if i > 0 {
21131                        self.write_space();
21132                    }
21133                    // field is an In expression: column IN (values)
21134                    self.generate_expression(field)?;
21135                }
21136            }
21137
21138            // DEFAULT ON NULL
21139            if let Some(default_val) = &pivot.default_on_null {
21140                self.write_space();
21141                self.write_keyword("DEFAULT ON NULL");
21142                self.write(" (");
21143                self.generate_expression(default_val)?;
21144                self.write(")");
21145            }
21146
21147            // GROUP BY inside PIVOT parens
21148            if let Some(group) = &pivot.group {
21149                self.write_space();
21150                self.generate_expression(group)?;
21151            }
21152
21153            self.write(")");
21154        }
21155
21156        // Alias
21157        if let Some(alias) = &pivot.alias {
21158            self.write_space();
21159            self.write_keyword("AS");
21160            self.write_space();
21161            self.generate_identifier(alias)?;
21162        }
21163
21164        Ok(())
21165    }
21166
21167    fn generate_unpivot(&mut self, unpivot: &Unpivot) -> Result<()> {
21168        self.generate_expression(&unpivot.this)?;
21169        self.write_space();
21170        self.write_keyword("UNPIVOT");
21171        // Output INCLUDE NULLS or EXCLUDE NULLS if specified
21172        if let Some(include) = unpivot.include_nulls {
21173            self.write_space();
21174            if include {
21175                self.write_keyword("INCLUDE NULLS");
21176            } else {
21177                self.write_keyword("EXCLUDE NULLS");
21178            }
21179            self.write_space();
21180        }
21181        self.write("(");
21182        if unpivot.value_column_parenthesized {
21183            self.write("(");
21184        }
21185        self.generate_identifier(&unpivot.value_column)?;
21186        // Output additional value columns if present
21187        for extra_col in &unpivot.extra_value_columns {
21188            self.write(", ");
21189            self.generate_identifier(extra_col)?;
21190        }
21191        if unpivot.value_column_parenthesized {
21192            self.write(")");
21193        }
21194        self.write_space();
21195        self.write_keyword("FOR");
21196        self.write_space();
21197        self.generate_identifier(&unpivot.name_column)?;
21198        self.write_space();
21199        self.write_keyword("IN");
21200        self.write(" (");
21201        for (i, col) in unpivot.columns.iter().enumerate() {
21202            if i > 0 {
21203                self.write(", ");
21204            }
21205            self.generate_expression(col)?;
21206        }
21207        self.write("))");
21208        if let Some(alias) = &unpivot.alias {
21209            self.write_space();
21210            self.write_keyword("AS");
21211            self.write_space();
21212            self.generate_identifier(alias)?;
21213        }
21214        Ok(())
21215    }
21216
21217    fn generate_values(&mut self, values: &Values) -> Result<()> {
21218        self.write_keyword("VALUES");
21219        for (i, row) in values.expressions.iter().enumerate() {
21220            if i > 0 {
21221                self.write(",");
21222            }
21223            self.write(" (");
21224            for (j, expr) in row.expressions.iter().enumerate() {
21225                if j > 0 {
21226                    self.write(", ");
21227                }
21228                self.generate_expression(expr)?;
21229            }
21230            self.write(")");
21231        }
21232        if let Some(alias) = &values.alias {
21233            self.write_space();
21234            self.write_keyword("AS");
21235            self.write_space();
21236            self.generate_identifier(alias)?;
21237            if !values.column_aliases.is_empty() {
21238                self.write("(");
21239                for (i, col) in values.column_aliases.iter().enumerate() {
21240                    if i > 0 {
21241                        self.write(", ");
21242                    }
21243                    self.generate_identifier(col)?;
21244                }
21245                self.write(")");
21246            }
21247        }
21248        Ok(())
21249    }
21250
21251    fn generate_array(&mut self, arr: &Array) -> Result<()> {
21252        // Apply struct name inheritance for target dialects that need it
21253        let needs_inheritance = matches!(
21254            self.config.dialect,
21255            Some(DialectType::DuckDB)
21256                | Some(DialectType::Spark)
21257                | Some(DialectType::Databricks)
21258                | Some(DialectType::Hive)
21259                | Some(DialectType::Snowflake)
21260                | Some(DialectType::Presto)
21261                | Some(DialectType::Trino)
21262        );
21263        let propagated: Vec<Expression>;
21264        let expressions = if needs_inheritance && arr.expressions.len() > 1 {
21265            propagated = Self::inherit_struct_field_names(&arr.expressions);
21266            &propagated
21267        } else {
21268            &arr.expressions
21269        };
21270
21271        // Generic mode: ARRAY(1, 2, 3) with parentheses
21272        // Dialect mode: ARRAY[1, 2, 3] with brackets (or just [1, 2, 3] if array_bracket_only)
21273        let use_parens =
21274            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
21275        if !self.config.array_bracket_only {
21276            self.write_keyword("ARRAY");
21277        }
21278        if use_parens {
21279            self.write("(");
21280        } else {
21281            self.write("[");
21282        }
21283        for (i, expr) in expressions.iter().enumerate() {
21284            if i > 0 {
21285                self.write(", ");
21286            }
21287            self.generate_expression(expr)?;
21288        }
21289        if use_parens {
21290            self.write(")");
21291        } else {
21292            self.write("]");
21293        }
21294        Ok(())
21295    }
21296
21297    fn generate_tuple(&mut self, tuple: &Tuple) -> Result<()> {
21298        // Special case: Tuple(function/expr, TableAlias) pattern for table functions with typed aliases
21299        // Used for PostgreSQL functions like JSON_TO_RECORDSET: FUNC(args) AS alias(col1 type1, col2 type2)
21300        if tuple.expressions.len() == 2 {
21301            if let Expression::TableAlias(_) = &tuple.expressions[1] {
21302                // First element is the function/expression, second is the TableAlias
21303                self.generate_expression(&tuple.expressions[0])?;
21304                self.write_space();
21305                self.write_keyword("AS");
21306                self.write_space();
21307                self.generate_expression(&tuple.expressions[1])?;
21308                return Ok(());
21309            }
21310        }
21311
21312        // In pretty mode, format long tuples with each element on a new line
21313        // Only expand if total width exceeds threshold
21314        let expand_tuple = if self.config.pretty && tuple.expressions.len() > 1 {
21315            let mut expr_strings: Vec<String> = Vec::with_capacity(tuple.expressions.len());
21316            for expr in &tuple.expressions {
21317                expr_strings.push(self.generate_to_string(expr)?);
21318            }
21319            self.too_wide(&expr_strings)
21320        } else {
21321            false
21322        };
21323
21324        if expand_tuple {
21325            self.write("(");
21326            self.write_newline();
21327            self.indent_level += 1;
21328            for (i, expr) in tuple.expressions.iter().enumerate() {
21329                if i > 0 {
21330                    self.write(",");
21331                    self.write_newline();
21332                }
21333                self.write_indent();
21334                self.generate_expression(expr)?;
21335            }
21336            self.indent_level -= 1;
21337            self.write_newline();
21338            self.write_indent();
21339            self.write(")");
21340        } else {
21341            self.write("(");
21342            for (i, expr) in tuple.expressions.iter().enumerate() {
21343                if i > 0 {
21344                    self.write(", ");
21345                }
21346                self.generate_expression(expr)?;
21347            }
21348            self.write(")");
21349        }
21350        Ok(())
21351    }
21352
21353    fn generate_pipe_operator(&mut self, pipe: &PipeOperator) -> Result<()> {
21354        self.generate_expression(&pipe.this)?;
21355        self.write(" |> ");
21356        self.generate_expression(&pipe.expression)?;
21357        Ok(())
21358    }
21359
21360    fn generate_ordered(&mut self, ordered: &Ordered) -> Result<()> {
21361        self.generate_expression(&ordered.this)?;
21362        if ordered.desc {
21363            self.write_space();
21364            self.write_keyword("DESC");
21365        } else if ordered.explicit_asc {
21366            self.write_space();
21367            self.write_keyword("ASC");
21368        }
21369        if let Some(nulls_first) = ordered.nulls_first {
21370            // Determine if we should skip outputting NULLS FIRST/LAST when it's the default
21371            // for the dialect. Different dialects have different NULL ordering defaults:
21372            //
21373            // nulls_are_large (Oracle, Postgres, Snowflake, etc.):
21374            //   - ASC: NULLS LAST is default (omit NULLS LAST for ASC)
21375            //   - DESC: NULLS FIRST is default (omit NULLS FIRST for DESC)
21376            //
21377            // nulls_are_small (Spark, Hive, BigQuery, most others):
21378            //   - ASC: NULLS FIRST is default
21379            //   - DESC: NULLS LAST is default
21380            //
21381            // nulls_are_last (DuckDB, Presto, Trino, Dremio, etc.):
21382            //   - NULLS LAST is always the default regardless of sort direction
21383            let is_asc = !ordered.desc;
21384            let is_nulls_are_large = matches!(
21385                self.config.dialect,
21386                Some(DialectType::Oracle)
21387                    | Some(DialectType::PostgreSQL)
21388                    | Some(DialectType::Redshift)
21389                    | Some(DialectType::Snowflake)
21390            );
21391            let is_nulls_are_last = matches!(
21392                self.config.dialect,
21393                Some(DialectType::Dremio)
21394                    | Some(DialectType::DuckDB)
21395                    | Some(DialectType::Presto)
21396                    | Some(DialectType::Trino)
21397                    | Some(DialectType::Athena)
21398                    | Some(DialectType::ClickHouse)
21399                    | Some(DialectType::Drill)
21400                    | Some(DialectType::Exasol)
21401            );
21402
21403            // Check if the NULLS ordering matches the default for this dialect
21404            let is_default_nulls = if is_nulls_are_large {
21405                // For nulls_are_large: ASC + NULLS LAST or DESC + NULLS FIRST is default
21406                (is_asc && !nulls_first) || (!is_asc && nulls_first)
21407            } else if is_nulls_are_last {
21408                // For nulls_are_last: NULLS LAST is always default
21409                !nulls_first
21410            } else {
21411                false
21412            };
21413
21414            if !is_default_nulls {
21415                self.write_space();
21416                self.write_keyword("NULLS");
21417                self.write_space();
21418                self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
21419            }
21420        }
21421        // WITH FILL clause (ClickHouse)
21422        if let Some(ref with_fill) = ordered.with_fill {
21423            self.write_space();
21424            self.generate_with_fill(with_fill)?;
21425        }
21426        Ok(())
21427    }
21428
21429    /// Write a ClickHouse type string, wrapping in Nullable unless in map key context.
21430    fn write_clickhouse_type(&mut self, type_str: &str) {
21431        if self.clickhouse_nullable_depth < 0 {
21432            // Map key context: don't wrap in Nullable
21433            self.write(type_str);
21434        } else {
21435            self.write(&format!("Nullable({})", type_str));
21436        }
21437    }
21438
21439    fn generate_data_type(&mut self, dt: &DataType) -> Result<()> {
21440        use crate::dialects::DialectType;
21441
21442        match dt {
21443            DataType::Boolean => {
21444                // Dialect-specific boolean type mappings
21445                match self.config.dialect {
21446                    Some(DialectType::TSQL) => self.write_keyword("BIT"),
21447                    Some(DialectType::MySQL) => self.write_keyword("BOOLEAN"), // alias for TINYINT(1)
21448                    Some(DialectType::Oracle) => {
21449                        // Oracle 23c+ supports BOOLEAN, older versions use NUMBER(1)
21450                        self.write_keyword("NUMBER(1)")
21451                    }
21452                    Some(DialectType::ClickHouse) => self.write("Bool"), // ClickHouse uses Bool (case-sensitive)
21453                    _ => self.write_keyword("BOOLEAN"),
21454                }
21455            }
21456            DataType::TinyInt { length } => {
21457                // PostgreSQL, Oracle, and Exasol don't have TINYINT, use SMALLINT
21458                // Dremio maps TINYINT to INT
21459                // ClickHouse maps TINYINT to Int8
21460                match self.config.dialect {
21461                    Some(DialectType::PostgreSQL)
21462                    | Some(DialectType::Redshift)
21463                    | Some(DialectType::Oracle)
21464                    | Some(DialectType::Exasol) => {
21465                        self.write_keyword("SMALLINT");
21466                    }
21467                    Some(DialectType::Teradata) => {
21468                        // Teradata uses BYTEINT for smallest integer
21469                        self.write_keyword("BYTEINT");
21470                    }
21471                    Some(DialectType::Dremio) => {
21472                        // Dremio maps TINYINT to INT
21473                        self.write_keyword("INT");
21474                    }
21475                    Some(DialectType::ClickHouse) => {
21476                        self.write_clickhouse_type("Int8");
21477                    }
21478                    _ => {
21479                        self.write_keyword("TINYINT");
21480                    }
21481                }
21482                if let Some(n) = length {
21483                    if !matches!(
21484                        self.config.dialect,
21485                        Some(DialectType::Dremio) | Some(DialectType::ClickHouse)
21486                    ) {
21487                        self.write(&format!("({})", n));
21488                    }
21489                }
21490            }
21491            DataType::SmallInt { length } => {
21492                // Dremio maps SMALLINT to INT, SQLite/Drill maps SMALLINT to INTEGER
21493                match self.config.dialect {
21494                    Some(DialectType::Dremio) => {
21495                        self.write_keyword("INT");
21496                    }
21497                    Some(DialectType::SQLite) | Some(DialectType::Drill) => {
21498                        self.write_keyword("INTEGER");
21499                    }
21500                    Some(DialectType::BigQuery) => {
21501                        self.write_keyword("INT64");
21502                    }
21503                    Some(DialectType::ClickHouse) => {
21504                        self.write_clickhouse_type("Int16");
21505                    }
21506                    _ => {
21507                        self.write_keyword("SMALLINT");
21508                        if let Some(n) = length {
21509                            self.write(&format!("({})", n));
21510                        }
21511                    }
21512                }
21513            }
21514            DataType::Int {
21515                length,
21516                integer_spelling,
21517            } => {
21518                // BigQuery uses INT64 for INT
21519                if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
21520                    self.write_keyword("INT64");
21521                } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
21522                    self.write_clickhouse_type("Int32");
21523                } else {
21524                    // TSQL, Presto, Trino, SQLite, Redshift use INTEGER as the canonical form
21525                    let use_integer = match self.config.dialect {
21526                        Some(DialectType::TSQL)
21527                        | Some(DialectType::Fabric)
21528                        | Some(DialectType::Presto)
21529                        | Some(DialectType::Trino)
21530                        | Some(DialectType::SQLite)
21531                        | Some(DialectType::Redshift) => true,
21532                        // Databricks preserves the original spelling
21533                        Some(DialectType::Databricks) => *integer_spelling,
21534                        _ => false,
21535                    };
21536                    if use_integer {
21537                        self.write_keyword("INTEGER");
21538                    } else {
21539                        self.write_keyword("INT");
21540                    }
21541                    if let Some(n) = length {
21542                        self.write(&format!("({})", n));
21543                    }
21544                }
21545            }
21546            DataType::BigInt { length } => {
21547                // Dialect-specific bigint type mappings
21548                match self.config.dialect {
21549                    Some(DialectType::Oracle) => {
21550                        // Oracle doesn't have BIGINT, uses INT
21551                        self.write_keyword("INT");
21552                    }
21553                    Some(DialectType::ClickHouse) => {
21554                        self.write_clickhouse_type("Int64");
21555                    }
21556                    _ => {
21557                        self.write_keyword("BIGINT");
21558                        if let Some(n) = length {
21559                            self.write(&format!("({})", n));
21560                        }
21561                    }
21562                }
21563            }
21564            DataType::Float {
21565                precision,
21566                scale,
21567                real_spelling,
21568            } => {
21569                // Dialect-specific float type mappings
21570                // If real_spelling is true, preserve REAL; otherwise use dialect default
21571                // Spark/Hive don't support REAL, always use FLOAT
21572                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
21573                    self.write_clickhouse_type("Float32");
21574                } else if *real_spelling
21575                    && !matches!(
21576                        self.config.dialect,
21577                        Some(DialectType::Spark)
21578                            | Some(DialectType::Databricks)
21579                            | Some(DialectType::Hive)
21580                            | Some(DialectType::Snowflake)
21581                            | Some(DialectType::MySQL)
21582                            | Some(DialectType::BigQuery)
21583                    )
21584                {
21585                    self.write_keyword("REAL")
21586                } else {
21587                    match self.config.dialect {
21588                        Some(DialectType::PostgreSQL) => self.write_keyword("REAL"),
21589                        Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
21590                        _ => self.write_keyword("FLOAT"),
21591                    }
21592                }
21593                // MySQL supports FLOAT(precision) or FLOAT(precision, scale)
21594                // Spark/Hive don't support FLOAT(precision)
21595                if !matches!(
21596                    self.config.dialect,
21597                    Some(DialectType::Spark)
21598                        | Some(DialectType::Databricks)
21599                        | Some(DialectType::Hive)
21600                        | Some(DialectType::Presto)
21601                        | Some(DialectType::Trino)
21602                ) {
21603                    if let Some(p) = precision {
21604                        self.write(&format!("({}", p));
21605                        if let Some(s) = scale {
21606                            self.write(&format!(", {})", s));
21607                        } else {
21608                            self.write(")");
21609                        }
21610                    }
21611                }
21612            }
21613            DataType::Double { precision, scale } => {
21614                // Dialect-specific double type mappings
21615                match self.config.dialect {
21616                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
21617                        self.write_keyword("FLOAT")
21618                    } // SQL Server/Fabric FLOAT is double
21619                    Some(DialectType::Oracle) => self.write_keyword("DOUBLE PRECISION"),
21620                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("Float64"),
21621                    Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
21622                    Some(DialectType::SQLite) => self.write_keyword("REAL"),
21623                    Some(DialectType::PostgreSQL)
21624                    | Some(DialectType::Redshift)
21625                    | Some(DialectType::Teradata)
21626                    | Some(DialectType::Materialize) => self.write_keyword("DOUBLE PRECISION"),
21627                    _ => self.write_keyword("DOUBLE"),
21628                }
21629                // MySQL supports DOUBLE(precision, scale)
21630                if let Some(p) = precision {
21631                    self.write(&format!("({}", p));
21632                    if let Some(s) = scale {
21633                        self.write(&format!(", {})", s));
21634                    } else {
21635                        self.write(")");
21636                    }
21637                }
21638            }
21639            DataType::Decimal { precision, scale } => {
21640                // Dialect-specific decimal type mappings
21641                match self.config.dialect {
21642                    Some(DialectType::ClickHouse) => {
21643                        self.write("Decimal");
21644                        if let Some(p) = precision {
21645                            self.write(&format!("({}", p));
21646                            if let Some(s) = scale {
21647                                self.write(&format!(", {}", s));
21648                            }
21649                            self.write(")");
21650                        }
21651                    }
21652                    Some(DialectType::Oracle) => {
21653                        // Oracle uses NUMBER instead of DECIMAL
21654                        self.write_keyword("NUMBER");
21655                        if let Some(p) = precision {
21656                            self.write(&format!("({}", p));
21657                            if let Some(s) = scale {
21658                                self.write(&format!(", {}", s));
21659                            }
21660                            self.write(")");
21661                        }
21662                    }
21663                    Some(DialectType::BigQuery) => {
21664                        // BigQuery uses NUMERIC instead of DECIMAL
21665                        self.write_keyword("NUMERIC");
21666                        if let Some(p) = precision {
21667                            self.write(&format!("({}", p));
21668                            if let Some(s) = scale {
21669                                self.write(&format!(", {}", s));
21670                            }
21671                            self.write(")");
21672                        }
21673                    }
21674                    _ => {
21675                        self.write_keyword("DECIMAL");
21676                        if let Some(p) = precision {
21677                            self.write(&format!("({}", p));
21678                            if let Some(s) = scale {
21679                                self.write(&format!(", {}", s));
21680                            }
21681                            self.write(")");
21682                        }
21683                    }
21684                }
21685            }
21686            DataType::Char { length } => {
21687                // Dialect-specific char type mappings
21688                match self.config.dialect {
21689                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
21690                        // DuckDB/SQLite maps CHAR to TEXT
21691                        self.write_keyword("TEXT");
21692                    }
21693                    Some(DialectType::Hive)
21694                    | Some(DialectType::Spark)
21695                    | Some(DialectType::Databricks) => {
21696                        // Hive/Spark/Databricks maps CHAR to STRING (when no length)
21697                        // CHAR(n) with explicit length is kept as CHAR(n) for Spark/Databricks
21698                        if length.is_some()
21699                            && !matches!(self.config.dialect, Some(DialectType::Hive))
21700                        {
21701                            self.write_keyword("CHAR");
21702                            if let Some(n) = length {
21703                                self.write(&format!("({})", n));
21704                            }
21705                        } else {
21706                            self.write_keyword("STRING");
21707                        }
21708                    }
21709                    Some(DialectType::Dremio) => {
21710                        // Dremio maps CHAR to VARCHAR
21711                        self.write_keyword("VARCHAR");
21712                        if let Some(n) = length {
21713                            self.write(&format!("({})", n));
21714                        }
21715                    }
21716                    _ => {
21717                        self.write_keyword("CHAR");
21718                        if let Some(n) = length {
21719                            self.write(&format!("({})", n));
21720                        }
21721                    }
21722                }
21723            }
21724            DataType::VarChar {
21725                length,
21726                parenthesized_length,
21727            } => {
21728                // Dialect-specific varchar type mappings
21729                match self.config.dialect {
21730                    Some(DialectType::Oracle) => {
21731                        self.write_keyword("VARCHAR2");
21732                        if let Some(n) = length {
21733                            self.write(&format!("({})", n));
21734                        }
21735                    }
21736                    Some(DialectType::DuckDB) => {
21737                        // DuckDB maps VARCHAR to TEXT, preserving length
21738                        self.write_keyword("TEXT");
21739                        if let Some(n) = length {
21740                            self.write(&format!("({})", n));
21741                        }
21742                    }
21743                    Some(DialectType::SQLite) => {
21744                        // SQLite maps VARCHAR to TEXT, preserving length
21745                        self.write_keyword("TEXT");
21746                        if let Some(n) = length {
21747                            self.write(&format!("({})", n));
21748                        }
21749                    }
21750                    Some(DialectType::MySQL) if length.is_none() => {
21751                        // MySQL requires VARCHAR to have a size - if it doesn't, use TEXT
21752                        self.write_keyword("TEXT");
21753                    }
21754                    Some(DialectType::Hive)
21755                    | Some(DialectType::Spark)
21756                    | Some(DialectType::Databricks)
21757                        if length.is_none() =>
21758                    {
21759                        // Hive/Spark/Databricks: VARCHAR without length → STRING
21760                        self.write_keyword("STRING");
21761                    }
21762                    _ => {
21763                        self.write_keyword("VARCHAR");
21764                        if let Some(n) = length {
21765                            // Hive uses VARCHAR((n)) with extra parentheses in STRUCT definitions
21766                            if *parenthesized_length {
21767                                self.write(&format!("(({}))", n));
21768                            } else {
21769                                self.write(&format!("({})", n));
21770                            }
21771                        }
21772                    }
21773                }
21774            }
21775            DataType::Text => {
21776                // Dialect-specific text type mappings
21777                match self.config.dialect {
21778                    Some(DialectType::Oracle) => self.write_keyword("CLOB"),
21779                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
21780                        self.write_keyword("VARCHAR(MAX)")
21781                    }
21782                    Some(DialectType::BigQuery) => self.write_keyword("STRING"),
21783                    Some(DialectType::Snowflake)
21784                    | Some(DialectType::Dremio)
21785                    | Some(DialectType::Drill) => self.write_keyword("VARCHAR"),
21786                    Some(DialectType::Exasol) => self.write_keyword("LONG VARCHAR"),
21787                    Some(DialectType::Presto)
21788                    | Some(DialectType::Trino)
21789                    | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
21790                    Some(DialectType::Spark)
21791                    | Some(DialectType::Databricks)
21792                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
21793                    Some(DialectType::Redshift) => self.write_keyword("VARCHAR(MAX)"),
21794                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
21795                        self.write_keyword("STRING")
21796                    }
21797                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
21798                    _ => self.write_keyword("TEXT"),
21799                }
21800            }
21801            DataType::TextWithLength { length } => {
21802                // TEXT(n) - dialect-specific type with length
21803                match self.config.dialect {
21804                    Some(DialectType::Oracle) => self.write(&format!("CLOB({})", length)),
21805                    Some(DialectType::Hive)
21806                    | Some(DialectType::Spark)
21807                    | Some(DialectType::Databricks) => {
21808                        self.write(&format!("VARCHAR({})", length));
21809                    }
21810                    Some(DialectType::Redshift) => self.write(&format!("VARCHAR({})", length)),
21811                    Some(DialectType::BigQuery) => self.write(&format!("STRING({})", length)),
21812                    Some(DialectType::Snowflake)
21813                    | Some(DialectType::Presto)
21814                    | Some(DialectType::Trino)
21815                    | Some(DialectType::Athena)
21816                    | Some(DialectType::Drill)
21817                    | Some(DialectType::Dremio) => {
21818                        self.write(&format!("VARCHAR({})", length));
21819                    }
21820                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
21821                        self.write(&format!("VARCHAR({})", length))
21822                    }
21823                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
21824                        self.write(&format!("STRING({})", length))
21825                    }
21826                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
21827                    _ => self.write(&format!("TEXT({})", length)),
21828                }
21829            }
21830            DataType::String { length } => {
21831                // STRING type with optional length (BigQuery STRING(n))
21832                match self.config.dialect {
21833                    Some(DialectType::ClickHouse) => {
21834                        // ClickHouse uses String with specific casing
21835                        self.write("String");
21836                        if let Some(n) = length {
21837                            self.write(&format!("({})", n));
21838                        }
21839                    }
21840                    Some(DialectType::BigQuery)
21841                    | Some(DialectType::Hive)
21842                    | Some(DialectType::Spark)
21843                    | Some(DialectType::Databricks)
21844                    | Some(DialectType::StarRocks)
21845                    | Some(DialectType::Doris) => {
21846                        self.write_keyword("STRING");
21847                        if let Some(n) = length {
21848                            self.write(&format!("({})", n));
21849                        }
21850                    }
21851                    Some(DialectType::PostgreSQL) => {
21852                        // PostgreSQL doesn't have STRING - use VARCHAR or TEXT
21853                        if let Some(n) = length {
21854                            self.write_keyword("VARCHAR");
21855                            self.write(&format!("({})", n));
21856                        } else {
21857                            self.write_keyword("TEXT");
21858                        }
21859                    }
21860                    Some(DialectType::Redshift) => {
21861                        // Redshift: STRING -> VARCHAR(MAX)
21862                        if let Some(n) = length {
21863                            self.write_keyword("VARCHAR");
21864                            self.write(&format!("({})", n));
21865                        } else {
21866                            self.write_keyword("VARCHAR(MAX)");
21867                        }
21868                    }
21869                    Some(DialectType::MySQL) => {
21870                        // MySQL doesn't have STRING - use VARCHAR or TEXT
21871                        if let Some(n) = length {
21872                            self.write_keyword("VARCHAR");
21873                            self.write(&format!("({})", n));
21874                        } else {
21875                            self.write_keyword("TEXT");
21876                        }
21877                    }
21878                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
21879                        // TSQL: STRING -> VARCHAR(MAX)
21880                        if let Some(n) = length {
21881                            self.write_keyword("VARCHAR");
21882                            self.write(&format!("({})", n));
21883                        } else {
21884                            self.write_keyword("VARCHAR(MAX)");
21885                        }
21886                    }
21887                    Some(DialectType::Oracle) => {
21888                        // Oracle: STRING -> CLOB
21889                        self.write_keyword("CLOB");
21890                    }
21891                    Some(DialectType::DuckDB) | Some(DialectType::Materialize) => {
21892                        // DuckDB/Materialize uses TEXT for string types
21893                        self.write_keyword("TEXT");
21894                        if let Some(n) = length {
21895                            self.write(&format!("({})", n));
21896                        }
21897                    }
21898                    Some(DialectType::Presto)
21899                    | Some(DialectType::Trino)
21900                    | Some(DialectType::Drill)
21901                    | Some(DialectType::Dremio) => {
21902                        // Presto/Trino/Drill use VARCHAR for string types
21903                        self.write_keyword("VARCHAR");
21904                        if let Some(n) = length {
21905                            self.write(&format!("({})", n));
21906                        }
21907                    }
21908                    Some(DialectType::Snowflake) => {
21909                        // Snowflake: STRING stays as STRING (identity/DDL)
21910                        // CAST context STRING -> VARCHAR is handled in generate_cast
21911                        self.write_keyword("STRING");
21912                        if let Some(n) = length {
21913                            self.write(&format!("({})", n));
21914                        }
21915                    }
21916                    _ => {
21917                        // Default: output STRING with optional length
21918                        self.write_keyword("STRING");
21919                        if let Some(n) = length {
21920                            self.write(&format!("({})", n));
21921                        }
21922                    }
21923                }
21924            }
21925            DataType::Binary { length } => {
21926                // Dialect-specific binary type mappings
21927                match self.config.dialect {
21928                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
21929                        self.write_keyword("BYTEA");
21930                        if let Some(n) = length {
21931                            self.write(&format!("({})", n));
21932                        }
21933                    }
21934                    Some(DialectType::Redshift) => {
21935                        self.write_keyword("VARBYTE");
21936                        if let Some(n) = length {
21937                            self.write(&format!("({})", n));
21938                        }
21939                    }
21940                    Some(DialectType::DuckDB)
21941                    | Some(DialectType::SQLite)
21942                    | Some(DialectType::Oracle) => {
21943                        // DuckDB/SQLite/Oracle maps BINARY to BLOB
21944                        self.write_keyword("BLOB");
21945                        if let Some(n) = length {
21946                            self.write(&format!("({})", n));
21947                        }
21948                    }
21949                    Some(DialectType::Presto)
21950                    | Some(DialectType::Trino)
21951                    | Some(DialectType::Athena)
21952                    | Some(DialectType::Drill)
21953                    | Some(DialectType::Dremio) => {
21954                        // These dialects map BINARY to VARBINARY
21955                        self.write_keyword("VARBINARY");
21956                        if let Some(n) = length {
21957                            self.write(&format!("({})", n));
21958                        }
21959                    }
21960                    Some(DialectType::ClickHouse) => {
21961                        // ClickHouse: wrap BINARY in Nullable (unless map key context)
21962                        if self.clickhouse_nullable_depth < 0 {
21963                            self.write("BINARY");
21964                        } else {
21965                            self.write("Nullable(BINARY");
21966                        }
21967                        if let Some(n) = length {
21968                            self.write(&format!("({})", n));
21969                        }
21970                        if self.clickhouse_nullable_depth >= 0 {
21971                            self.write(")");
21972                        }
21973                    }
21974                    _ => {
21975                        self.write_keyword("BINARY");
21976                        if let Some(n) = length {
21977                            self.write(&format!("({})", n));
21978                        }
21979                    }
21980                }
21981            }
21982            DataType::VarBinary { length } => {
21983                // Dialect-specific varbinary type mappings
21984                match self.config.dialect {
21985                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
21986                        self.write_keyword("BYTEA");
21987                        if let Some(n) = length {
21988                            self.write(&format!("({})", n));
21989                        }
21990                    }
21991                    Some(DialectType::Redshift) => {
21992                        self.write_keyword("VARBYTE");
21993                        if let Some(n) = length {
21994                            self.write(&format!("({})", n));
21995                        }
21996                    }
21997                    Some(DialectType::DuckDB)
21998                    | Some(DialectType::SQLite)
21999                    | Some(DialectType::Oracle) => {
22000                        // DuckDB/SQLite/Oracle maps VARBINARY to BLOB
22001                        self.write_keyword("BLOB");
22002                        if let Some(n) = length {
22003                            self.write(&format!("({})", n));
22004                        }
22005                    }
22006                    Some(DialectType::Exasol) => {
22007                        // Exasol maps VARBINARY to VARCHAR
22008                        self.write_keyword("VARCHAR");
22009                    }
22010                    Some(DialectType::Spark)
22011                    | Some(DialectType::Hive)
22012                    | Some(DialectType::Databricks) => {
22013                        // Spark/Hive use BINARY instead of VARBINARY
22014                        self.write_keyword("BINARY");
22015                        if let Some(n) = length {
22016                            self.write(&format!("({})", n));
22017                        }
22018                    }
22019                    Some(DialectType::ClickHouse) => {
22020                        // ClickHouse maps VARBINARY to String (wrapped in Nullable unless map key)
22021                        self.write_clickhouse_type("String");
22022                    }
22023                    _ => {
22024                        self.write_keyword("VARBINARY");
22025                        if let Some(n) = length {
22026                            self.write(&format!("({})", n));
22027                        }
22028                    }
22029                }
22030            }
22031            DataType::Blob => {
22032                // Dialect-specific blob type mappings
22033                match self.config.dialect {
22034                    Some(DialectType::PostgreSQL) => self.write_keyword("BYTEA"),
22035                    Some(DialectType::Redshift) => self.write_keyword("VARBYTE"),
22036                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
22037                        self.write_keyword("VARBINARY")
22038                    }
22039                    Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
22040                    Some(DialectType::Exasol) => self.write_keyword("VARCHAR"),
22041                    Some(DialectType::Presto)
22042                    | Some(DialectType::Trino)
22043                    | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
22044                    Some(DialectType::DuckDB) => {
22045                        // Python sqlglot: BLOB -> VARBINARY for DuckDB (base TYPE_MAPPING)
22046                        // DuckDB identity works via: BLOB -> transform VarBinary -> generator BLOB
22047                        self.write_keyword("VARBINARY");
22048                    }
22049                    Some(DialectType::Spark)
22050                    | Some(DialectType::Databricks)
22051                    | Some(DialectType::Hive) => self.write_keyword("BINARY"),
22052                    Some(DialectType::ClickHouse) => {
22053                        // BLOB maps to Nullable(String) in ClickHouse, even in column defs
22054                        // where we normally suppress Nullable wrapping (clickhouse_nullable_depth = -1).
22055                        // This matches Python sqlglot behavior.
22056                        self.write("Nullable(String)");
22057                    }
22058                    _ => self.write_keyword("BLOB"),
22059                }
22060            }
22061            DataType::Bit { length } => {
22062                // Dialect-specific bit type mappings
22063                match self.config.dialect {
22064                    Some(DialectType::Dremio)
22065                    | Some(DialectType::Spark)
22066                    | Some(DialectType::Databricks)
22067                    | Some(DialectType::Hive)
22068                    | Some(DialectType::Snowflake)
22069                    | Some(DialectType::BigQuery)
22070                    | Some(DialectType::Presto)
22071                    | Some(DialectType::Trino)
22072                    | Some(DialectType::ClickHouse)
22073                    | Some(DialectType::Redshift) => {
22074                        // These dialects don't support BIT type, use BOOLEAN
22075                        self.write_keyword("BOOLEAN");
22076                    }
22077                    _ => {
22078                        self.write_keyword("BIT");
22079                        if let Some(n) = length {
22080                            self.write(&format!("({})", n));
22081                        }
22082                    }
22083                }
22084            }
22085            DataType::VarBit { length } => {
22086                self.write_keyword("VARBIT");
22087                if let Some(n) = length {
22088                    self.write(&format!("({})", n));
22089                }
22090            }
22091            DataType::Date => self.write_keyword("DATE"),
22092            DataType::Time {
22093                precision,
22094                timezone,
22095            } => {
22096                if *timezone {
22097                    // Dialect-specific TIME WITH TIME ZONE output
22098                    match self.config.dialect {
22099                        Some(DialectType::DuckDB) => {
22100                            // DuckDB: TIMETZ (drops precision)
22101                            self.write_keyword("TIMETZ");
22102                        }
22103                        Some(DialectType::PostgreSQL) => {
22104                            // PostgreSQL: TIMETZ or TIMETZ(p)
22105                            self.write_keyword("TIMETZ");
22106                            if let Some(p) = precision {
22107                                self.write(&format!("({})", p));
22108                            }
22109                        }
22110                        _ => {
22111                            // Presto/Trino/Redshift/others: TIME(p) WITH TIME ZONE
22112                            self.write_keyword("TIME");
22113                            if let Some(p) = precision {
22114                                self.write(&format!("({})", p));
22115                            }
22116                            self.write_keyword(" WITH TIME ZONE");
22117                        }
22118                    }
22119                } else {
22120                    // Spark/Hive/Databricks: TIME -> TIMESTAMP (TIME not supported)
22121                    if matches!(
22122                        self.config.dialect,
22123                        Some(DialectType::Spark)
22124                            | Some(DialectType::Databricks)
22125                            | Some(DialectType::Hive)
22126                    ) {
22127                        self.write_keyword("TIMESTAMP");
22128                    } else {
22129                        self.write_keyword("TIME");
22130                        if let Some(p) = precision {
22131                            self.write(&format!("({})", p));
22132                        }
22133                    }
22134                }
22135            }
22136            DataType::Timestamp {
22137                precision,
22138                timezone,
22139            } => {
22140                // Dialect-specific timestamp type mappings
22141                match self.config.dialect {
22142                    Some(DialectType::ClickHouse) => {
22143                        self.write("DateTime");
22144                        if let Some(p) = precision {
22145                            self.write(&format!("({})", p));
22146                        }
22147                    }
22148                    Some(DialectType::TSQL) => {
22149                        if *timezone {
22150                            self.write_keyword("DATETIMEOFFSET");
22151                        } else {
22152                            self.write_keyword("DATETIME2");
22153                        }
22154                        if let Some(p) = precision {
22155                            self.write(&format!("({})", p));
22156                        }
22157                    }
22158                    Some(DialectType::MySQL) => {
22159                        // MySQL: TIMESTAMP stays as TIMESTAMP in DDL; CAST mapping handled separately
22160                        self.write_keyword("TIMESTAMP");
22161                        if let Some(p) = precision {
22162                            self.write(&format!("({})", p));
22163                        }
22164                    }
22165                    Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
22166                        // Doris/StarRocks: TIMESTAMP -> DATETIME
22167                        self.write_keyword("DATETIME");
22168                        if let Some(p) = precision {
22169                            self.write(&format!("({})", p));
22170                        }
22171                    }
22172                    Some(DialectType::BigQuery) => {
22173                        // BigQuery: TIMESTAMP is always UTC, DATETIME is timezone-naive
22174                        if *timezone {
22175                            self.write_keyword("TIMESTAMP");
22176                        } else {
22177                            self.write_keyword("DATETIME");
22178                        }
22179                    }
22180                    Some(DialectType::DuckDB) => {
22181                        // DuckDB: TIMESTAMPTZ shorthand
22182                        if *timezone {
22183                            self.write_keyword("TIMESTAMPTZ");
22184                        } else {
22185                            self.write_keyword("TIMESTAMP");
22186                            if let Some(p) = precision {
22187                                self.write(&format!("({})", p));
22188                            }
22189                        }
22190                    }
22191                    _ => {
22192                        if *timezone && !self.config.tz_to_with_time_zone {
22193                            // Use TIMESTAMPTZ shorthand when dialect doesn't prefer WITH TIME ZONE
22194                            self.write_keyword("TIMESTAMPTZ");
22195                            if let Some(p) = precision {
22196                                self.write(&format!("({})", p));
22197                            }
22198                        } else {
22199                            self.write_keyword("TIMESTAMP");
22200                            if let Some(p) = precision {
22201                                self.write(&format!("({})", p));
22202                            }
22203                            if *timezone {
22204                                self.write_space();
22205                                self.write_keyword("WITH TIME ZONE");
22206                            }
22207                        }
22208                    }
22209                }
22210            }
22211            DataType::Interval { unit, to } => {
22212                self.write_keyword("INTERVAL");
22213                if let Some(u) = unit {
22214                    self.write_space();
22215                    self.write_keyword(u);
22216                }
22217                // Handle range intervals like DAY TO HOUR
22218                if let Some(t) = to {
22219                    self.write_space();
22220                    self.write_keyword("TO");
22221                    self.write_space();
22222                    self.write_keyword(t);
22223                }
22224            }
22225            DataType::Json => {
22226                // Dialect-specific JSON type mappings
22227                match self.config.dialect {
22228                    Some(DialectType::Oracle) => self.write_keyword("JSON"), // Oracle 21c+
22229                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"), // No native JSON type
22230                    Some(DialectType::MySQL) => self.write_keyword("JSON"),
22231                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
22232                    _ => self.write_keyword("JSON"),
22233                }
22234            }
22235            DataType::JsonB => {
22236                // JSONB is PostgreSQL specific, but Doris also supports it
22237                match self.config.dialect {
22238                    Some(DialectType::PostgreSQL) => self.write_keyword("JSONB"),
22239                    Some(DialectType::Doris) => self.write_keyword("JSONB"),
22240                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
22241                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
22242                    Some(DialectType::DuckDB) => self.write_keyword("JSON"), // DuckDB maps JSONB to JSON
22243                    _ => self.write_keyword("JSON"), // Fall back to JSON for other dialects
22244                }
22245            }
22246            DataType::Uuid => {
22247                // Dialect-specific UUID type mappings
22248                match self.config.dialect {
22249                    Some(DialectType::TSQL) => self.write_keyword("UNIQUEIDENTIFIER"),
22250                    Some(DialectType::MySQL) => self.write_keyword("CHAR(36)"),
22251                    Some(DialectType::Oracle) => self.write_keyword("RAW(16)"),
22252                    Some(DialectType::BigQuery)
22253                    | Some(DialectType::Spark)
22254                    | Some(DialectType::Databricks) => self.write_keyword("STRING"),
22255                    _ => self.write_keyword("UUID"),
22256                }
22257            }
22258            DataType::Array {
22259                element_type,
22260                dimension,
22261            } => {
22262                // Dialect-specific array syntax
22263                match self.config.dialect {
22264                    Some(DialectType::PostgreSQL)
22265                    | Some(DialectType::Redshift)
22266                    | Some(DialectType::DuckDB) => {
22267                        // PostgreSQL uses TYPE[] or TYPE[N] syntax
22268                        self.generate_data_type(element_type)?;
22269                        if let Some(dim) = dimension {
22270                            self.write(&format!("[{}]", dim));
22271                        } else {
22272                            self.write("[]");
22273                        }
22274                    }
22275                    Some(DialectType::BigQuery) => {
22276                        self.write_keyword("ARRAY<");
22277                        self.generate_data_type(element_type)?;
22278                        self.write(">");
22279                    }
22280                    Some(DialectType::Snowflake)
22281                    | Some(DialectType::Presto)
22282                    | Some(DialectType::Trino)
22283                    | Some(DialectType::ClickHouse) => {
22284                        // These dialects use Array(TYPE) parentheses syntax
22285                        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
22286                            self.write("Array(");
22287                        } else {
22288                            self.write_keyword("ARRAY(");
22289                        }
22290                        self.generate_data_type(element_type)?;
22291                        self.write(")");
22292                    }
22293                    Some(DialectType::TSQL)
22294                    | Some(DialectType::MySQL)
22295                    | Some(DialectType::Oracle) => {
22296                        // These dialects don't have native array types
22297                        // Fall back to JSON or use native workarounds
22298                        match self.config.dialect {
22299                            Some(DialectType::MySQL) => self.write_keyword("JSON"),
22300                            Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
22301                            _ => self.write_keyword("JSON"),
22302                        }
22303                    }
22304                    _ => {
22305                        // Default: use angle bracket syntax (ARRAY<T>)
22306                        self.write_keyword("ARRAY<");
22307                        self.generate_data_type(element_type)?;
22308                        self.write(">");
22309                    }
22310                }
22311            }
22312            DataType::List { element_type } => {
22313                // Materialize: element_type LIST (postfix syntax)
22314                self.generate_data_type(element_type)?;
22315                self.write_keyword(" LIST");
22316            }
22317            DataType::Map {
22318                key_type,
22319                value_type,
22320            } => {
22321                // Use parentheses for Snowflake and RisingWave, bracket syntax for Materialize, angle brackets for others
22322                match self.config.dialect {
22323                    Some(DialectType::Materialize) => {
22324                        // Materialize: MAP[key_type => value_type]
22325                        self.write_keyword("MAP[");
22326                        self.generate_data_type(key_type)?;
22327                        self.write(" => ");
22328                        self.generate_data_type(value_type)?;
22329                        self.write("]");
22330                    }
22331                    Some(DialectType::Snowflake)
22332                    | Some(DialectType::RisingWave)
22333                    | Some(DialectType::DuckDB)
22334                    | Some(DialectType::Presto)
22335                    | Some(DialectType::Trino)
22336                    | Some(DialectType::Athena) => {
22337                        self.write_keyword("MAP(");
22338                        self.generate_data_type(key_type)?;
22339                        self.write(", ");
22340                        self.generate_data_type(value_type)?;
22341                        self.write(")");
22342                    }
22343                    Some(DialectType::ClickHouse) => {
22344                        // ClickHouse: Map(key_type, value_type) with parenthesized syntax
22345                        // Key types must NOT be wrapped in Nullable
22346                        self.write("Map(");
22347                        self.clickhouse_nullable_depth = -1; // suppress Nullable for key
22348                        self.generate_data_type(key_type)?;
22349                        self.clickhouse_nullable_depth = 0;
22350                        self.write(", ");
22351                        self.generate_data_type(value_type)?;
22352                        self.write(")");
22353                    }
22354                    _ => {
22355                        self.write_keyword("MAP<");
22356                        self.generate_data_type(key_type)?;
22357                        self.write(", ");
22358                        self.generate_data_type(value_type)?;
22359                        self.write(">");
22360                    }
22361                }
22362            }
22363            DataType::Vector {
22364                element_type,
22365                dimension,
22366            } => {
22367                if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
22368                    // SingleStore format: VECTOR(dimension, type_alias)
22369                    self.write_keyword("VECTOR(");
22370                    if let Some(dim) = dimension {
22371                        self.write(&dim.to_string());
22372                    }
22373                    // Map type back to SingleStore alias
22374                    let type_alias = element_type.as_ref().and_then(|et| match et.as_ref() {
22375                        DataType::TinyInt { .. } => Some("I8"),
22376                        DataType::SmallInt { .. } => Some("I16"),
22377                        DataType::Int { .. } => Some("I32"),
22378                        DataType::BigInt { .. } => Some("I64"),
22379                        DataType::Float { .. } => Some("F32"),
22380                        DataType::Double { .. } => Some("F64"),
22381                        _ => None,
22382                    });
22383                    if let Some(alias) = type_alias {
22384                        if dimension.is_some() {
22385                            self.write(", ");
22386                        }
22387                        self.write(alias);
22388                    }
22389                    self.write(")");
22390                } else {
22391                    // Snowflake format: VECTOR(type, dimension)
22392                    self.write_keyword("VECTOR(");
22393                    if let Some(ref et) = element_type {
22394                        self.generate_data_type(et)?;
22395                        if dimension.is_some() {
22396                            self.write(", ");
22397                        }
22398                    }
22399                    if let Some(dim) = dimension {
22400                        self.write(&dim.to_string());
22401                    }
22402                    self.write(")");
22403                }
22404            }
22405            DataType::Object { fields, modifier } => {
22406                self.write_keyword("OBJECT(");
22407                for (i, (name, dt, not_null)) in fields.iter().enumerate() {
22408                    if i > 0 {
22409                        self.write(", ");
22410                    }
22411                    self.write(name);
22412                    self.write(" ");
22413                    self.generate_data_type(dt)?;
22414                    if *not_null {
22415                        self.write_keyword(" NOT NULL");
22416                    }
22417                }
22418                self.write(")");
22419                if let Some(mod_str) = modifier {
22420                    self.write(" ");
22421                    self.write_keyword(mod_str);
22422                }
22423            }
22424            DataType::Struct { fields, nested } => {
22425                // Dialect-specific struct type mappings
22426                match self.config.dialect {
22427                    Some(DialectType::Snowflake) => {
22428                        // Snowflake maps STRUCT to OBJECT
22429                        self.write_keyword("OBJECT(");
22430                        for (i, field) in fields.iter().enumerate() {
22431                            if i > 0 {
22432                                self.write(", ");
22433                            }
22434                            if !field.name.is_empty() {
22435                                self.write(&field.name);
22436                                self.write(" ");
22437                            }
22438                            self.generate_data_type(&field.data_type)?;
22439                        }
22440                        self.write(")");
22441                    }
22442                    Some(DialectType::Presto) | Some(DialectType::Trino) => {
22443                        // Presto/Trino use ROW(name TYPE, ...) syntax
22444                        self.write_keyword("ROW(");
22445                        for (i, field) in fields.iter().enumerate() {
22446                            if i > 0 {
22447                                self.write(", ");
22448                            }
22449                            if !field.name.is_empty() {
22450                                self.write(&field.name);
22451                                self.write(" ");
22452                            }
22453                            self.generate_data_type(&field.data_type)?;
22454                        }
22455                        self.write(")");
22456                    }
22457                    Some(DialectType::DuckDB) => {
22458                        // DuckDB uses parenthesized syntax: STRUCT(name TYPE, ...)
22459                        self.write_keyword("STRUCT(");
22460                        for (i, field) in fields.iter().enumerate() {
22461                            if i > 0 {
22462                                self.write(", ");
22463                            }
22464                            if !field.name.is_empty() {
22465                                self.write(&field.name);
22466                                self.write(" ");
22467                            }
22468                            self.generate_data_type(&field.data_type)?;
22469                        }
22470                        self.write(")");
22471                    }
22472                    Some(DialectType::ClickHouse) => {
22473                        // ClickHouse uses Tuple(name TYPE, ...) for struct types
22474                        self.write("Tuple(");
22475                        for (i, field) in fields.iter().enumerate() {
22476                            if i > 0 {
22477                                self.write(", ");
22478                            }
22479                            if !field.name.is_empty() {
22480                                self.write(&field.name);
22481                                self.write(" ");
22482                            }
22483                            self.generate_data_type(&field.data_type)?;
22484                        }
22485                        self.write(")");
22486                    }
22487                    Some(DialectType::SingleStore) => {
22488                        // SingleStore uses RECORD(name TYPE, ...) for struct types
22489                        self.write_keyword("RECORD(");
22490                        for (i, field) in fields.iter().enumerate() {
22491                            if i > 0 {
22492                                self.write(", ");
22493                            }
22494                            if !field.name.is_empty() {
22495                                self.write(&field.name);
22496                                self.write(" ");
22497                            }
22498                            self.generate_data_type(&field.data_type)?;
22499                        }
22500                        self.write(")");
22501                    }
22502                    _ => {
22503                        // Hive/Spark always use angle bracket syntax: STRUCT<name: TYPE>
22504                        let force_angle_brackets = matches!(
22505                            self.config.dialect,
22506                            Some(DialectType::Hive)
22507                                | Some(DialectType::Spark)
22508                                | Some(DialectType::Databricks)
22509                        );
22510                        if *nested && !force_angle_brackets {
22511                            self.write_keyword("STRUCT(");
22512                            for (i, field) in fields.iter().enumerate() {
22513                                if i > 0 {
22514                                    self.write(", ");
22515                                }
22516                                if !field.name.is_empty() {
22517                                    self.write(&field.name);
22518                                    self.write(" ");
22519                                }
22520                                self.generate_data_type(&field.data_type)?;
22521                            }
22522                            self.write(")");
22523                        } else {
22524                            self.write_keyword("STRUCT<");
22525                            for (i, field) in fields.iter().enumerate() {
22526                                if i > 0 {
22527                                    self.write(", ");
22528                                }
22529                                if !field.name.is_empty() {
22530                                    // Named field: name TYPE (with configurable separator for Hive)
22531                                    self.write(&field.name);
22532                                    self.write(self.config.struct_field_sep);
22533                                }
22534                                // For anonymous fields, just output the type
22535                                self.generate_data_type(&field.data_type)?;
22536                                // Spark/Databricks: Output COMMENT clause if present
22537                                if let Some(comment) = &field.comment {
22538                                    self.write(" COMMENT '");
22539                                    self.write(comment);
22540                                    self.write("'");
22541                                }
22542                                // BigQuery: Output OPTIONS clause if present
22543                                if !field.options.is_empty() {
22544                                    self.write(" ");
22545                                    self.generate_options_clause(&field.options)?;
22546                                }
22547                            }
22548                            self.write(">");
22549                        }
22550                    }
22551                }
22552            }
22553            DataType::Enum {
22554                values,
22555                assignments,
22556            } => {
22557                // DuckDB ENUM type: ENUM('RED', 'GREEN', 'BLUE')
22558                // ClickHouse: Enum('hello' = 1, 'world' = 2)
22559                if self.config.dialect == Some(DialectType::ClickHouse) {
22560                    self.write("Enum(");
22561                } else {
22562                    self.write_keyword("ENUM(");
22563                }
22564                for (i, val) in values.iter().enumerate() {
22565                    if i > 0 {
22566                        self.write(", ");
22567                    }
22568                    self.write("'");
22569                    self.write(val);
22570                    self.write("'");
22571                    if let Some(Some(assignment)) = assignments.get(i) {
22572                        self.write(" = ");
22573                        self.write(assignment);
22574                    }
22575                }
22576                self.write(")");
22577            }
22578            DataType::Set { values } => {
22579                // MySQL SET type: SET('a', 'b', 'c')
22580                self.write_keyword("SET(");
22581                for (i, val) in values.iter().enumerate() {
22582                    if i > 0 {
22583                        self.write(", ");
22584                    }
22585                    self.write("'");
22586                    self.write(val);
22587                    self.write("'");
22588                }
22589                self.write(")");
22590            }
22591            DataType::Union { fields } => {
22592                // DuckDB UNION type: UNION(num INT, str TEXT)
22593                self.write_keyword("UNION(");
22594                for (i, (name, dt)) in fields.iter().enumerate() {
22595                    if i > 0 {
22596                        self.write(", ");
22597                    }
22598                    if !name.is_empty() {
22599                        self.write(name);
22600                        self.write(" ");
22601                    }
22602                    self.generate_data_type(dt)?;
22603                }
22604                self.write(")");
22605            }
22606            DataType::Nullable { inner } => {
22607                // ClickHouse: Nullable(T), other dialects: just the inner type
22608                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
22609                    self.write("Nullable(");
22610                    // Suppress inner Nullable wrapping to prevent Nullable(Nullable(...))
22611                    let saved_depth = self.clickhouse_nullable_depth;
22612                    self.clickhouse_nullable_depth = -1;
22613                    self.generate_data_type(inner)?;
22614                    self.clickhouse_nullable_depth = saved_depth;
22615                    self.write(")");
22616                } else {
22617                    // Map ClickHouse-specific custom type names to standard types
22618                    match inner.as_ref() {
22619                        DataType::Custom { name } if name.to_uppercase() == "DATETIME" => {
22620                            self.generate_data_type(&DataType::Timestamp {
22621                                precision: None,
22622                                timezone: false,
22623                            })?;
22624                        }
22625                        _ => {
22626                            self.generate_data_type(inner)?;
22627                        }
22628                    }
22629                }
22630            }
22631            DataType::Custom { name } => {
22632                // Handle dialect-specific type transformations
22633                let name_upper = name.to_uppercase();
22634                match self.config.dialect {
22635                    Some(DialectType::ClickHouse) => {
22636                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
22637                            (name_upper[..idx].to_string(), &name[idx..])
22638                        } else {
22639                            (name_upper.clone(), "")
22640                        };
22641                        let mapped = match base_upper.as_str() {
22642                            "DATETIME" | "TIMESTAMPTZ" | "TIMESTAMP" | "TIMESTAMPNTZ"
22643                            | "SMALLDATETIME" | "DATETIME2" => "DateTime",
22644                            "DATETIME64" => "DateTime64",
22645                            "DATE32" => "Date32",
22646                            "INT" => "Int32",
22647                            "MEDIUMINT" => "Int32",
22648                            "INT8" => "Int8",
22649                            "INT16" => "Int16",
22650                            "INT32" => "Int32",
22651                            "INT64" => "Int64",
22652                            "INT128" => "Int128",
22653                            "INT256" => "Int256",
22654                            "UINT8" => "UInt8",
22655                            "UINT16" => "UInt16",
22656                            "UINT32" => "UInt32",
22657                            "UINT64" => "UInt64",
22658                            "UINT128" => "UInt128",
22659                            "UINT256" => "UInt256",
22660                            "FLOAT32" => "Float32",
22661                            "FLOAT64" => "Float64",
22662                            "DECIMAL32" => "Decimal32",
22663                            "DECIMAL64" => "Decimal64",
22664                            "DECIMAL128" => "Decimal128",
22665                            "DECIMAL256" => "Decimal256",
22666                            "ENUM" => "Enum",
22667                            "ENUM8" => "Enum8",
22668                            "ENUM16" => "Enum16",
22669                            "FIXEDSTRING" => "FixedString",
22670                            "NESTED" => "Nested",
22671                            "LOWCARDINALITY" => "LowCardinality",
22672                            "NULLABLE" => "Nullable",
22673                            "IPV4" => "IPv4",
22674                            "IPV6" => "IPv6",
22675                            "POINT" => "Point",
22676                            "RING" => "Ring",
22677                            "LINESTRING" => "LineString",
22678                            "MULTILINESTRING" => "MultiLineString",
22679                            "POLYGON" => "Polygon",
22680                            "MULTIPOLYGON" => "MultiPolygon",
22681                            "AGGREGATEFUNCTION" => "AggregateFunction",
22682                            "SIMPLEAGGREGATEFUNCTION" => "SimpleAggregateFunction",
22683                            "DYNAMIC" => "Dynamic",
22684                            _ => "",
22685                        };
22686                        if mapped.is_empty() {
22687                            self.write(name);
22688                        } else {
22689                            self.write(mapped);
22690                            self.write(suffix);
22691                        }
22692                    }
22693                    Some(DialectType::MySQL)
22694                        if name_upper == "TIMESTAMPTZ" || name_upper == "TIMESTAMPLTZ" =>
22695                    {
22696                        // MySQL doesn't support TIMESTAMPTZ/TIMESTAMPLTZ, use TIMESTAMP
22697                        self.write_keyword("TIMESTAMP");
22698                    }
22699                    Some(DialectType::TSQL) if name_upper == "VARIANT" => {
22700                        self.write_keyword("SQL_VARIANT");
22701                    }
22702                    Some(DialectType::DuckDB) if name_upper == "DECFLOAT" => {
22703                        self.write_keyword("DECIMAL(38, 5)");
22704                    }
22705                    Some(DialectType::Exasol) => {
22706                        // Exasol type mappings for custom types
22707                        match name_upper.as_str() {
22708                            // Binary types → VARCHAR
22709                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => self.write_keyword("VARCHAR"),
22710                            // Text types → VARCHAR (TEXT → LONG VARCHAR is handled by DataType::Text)
22711                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => self.write_keyword("VARCHAR"),
22712                            // Integer types
22713                            "MEDIUMINT" => self.write_keyword("INT"),
22714                            // Decimal types → DECIMAL
22715                            "DECIMAL32" | "DECIMAL64" | "DECIMAL128" | "DECIMAL256" => {
22716                                self.write_keyword("DECIMAL")
22717                            }
22718                            // Timestamp types
22719                            "DATETIME" => self.write_keyword("TIMESTAMP"),
22720                            "TIMESTAMPLTZ" => self.write_keyword("TIMESTAMP WITH LOCAL TIME ZONE"),
22721                            _ => self.write(name),
22722                        }
22723                    }
22724                    Some(DialectType::Dremio) => {
22725                        // Dremio type mappings for custom types
22726                        match name_upper.as_str() {
22727                            "TIMESTAMPNTZ" | "DATETIME" => self.write_keyword("TIMESTAMP"),
22728                            "ARRAY" => self.write_keyword("LIST"),
22729                            "NCHAR" => self.write_keyword("VARCHAR"),
22730                            _ => self.write(name),
22731                        }
22732                    }
22733                    // Map dialect-specific custom types to standard SQL types for other dialects
22734                    _ => {
22735                        // Extract base name and args for types with parenthesized args (e.g., DATETIME2(3))
22736                        let (base_upper, _args_str) = if let Some(idx) = name_upper.find('(') {
22737                            (name_upper[..idx].to_string(), Some(&name[idx..]))
22738                        } else {
22739                            (name_upper.clone(), None)
22740                        };
22741
22742                        match base_upper.as_str() {
22743                            "INT64"
22744                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
22745                            {
22746                                self.write_keyword("BIGINT");
22747                            }
22748                            "FLOAT64"
22749                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
22750                            {
22751                                self.write_keyword("DOUBLE");
22752                            }
22753                            "BOOL"
22754                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
22755                            {
22756                                self.write_keyword("BOOLEAN");
22757                            }
22758                            "BYTES"
22759                                if matches!(
22760                                    self.config.dialect,
22761                                    Some(DialectType::Spark)
22762                                        | Some(DialectType::Hive)
22763                                        | Some(DialectType::Databricks)
22764                                ) =>
22765                            {
22766                                self.write_keyword("BINARY");
22767                            }
22768                            "BYTES"
22769                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
22770                            {
22771                                self.write_keyword("VARBINARY");
22772                            }
22773                            // TSQL DATETIME2/SMALLDATETIME -> TIMESTAMP
22774                            "DATETIME2" | "SMALLDATETIME"
22775                                if !matches!(
22776                                    self.config.dialect,
22777                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
22778                                ) =>
22779                            {
22780                                // PostgreSQL preserves precision, others don't
22781                                if matches!(
22782                                    self.config.dialect,
22783                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
22784                                ) {
22785                                    self.write_keyword("TIMESTAMP");
22786                                    if let Some(args) = _args_str {
22787                                        self.write(args);
22788                                    }
22789                                } else {
22790                                    self.write_keyword("TIMESTAMP");
22791                                }
22792                            }
22793                            // TSQL DATETIMEOFFSET -> TIMESTAMPTZ
22794                            "DATETIMEOFFSET"
22795                                if !matches!(
22796                                    self.config.dialect,
22797                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
22798                                ) =>
22799                            {
22800                                if matches!(
22801                                    self.config.dialect,
22802                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
22803                                ) {
22804                                    self.write_keyword("TIMESTAMPTZ");
22805                                    if let Some(args) = _args_str {
22806                                        self.write(args);
22807                                    }
22808                                } else {
22809                                    self.write_keyword("TIMESTAMPTZ");
22810                                }
22811                            }
22812                            // TSQL UNIQUEIDENTIFIER -> UUID or STRING
22813                            "UNIQUEIDENTIFIER"
22814                                if !matches!(
22815                                    self.config.dialect,
22816                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
22817                                ) =>
22818                            {
22819                                match self.config.dialect {
22820                                    Some(DialectType::Spark)
22821                                    | Some(DialectType::Databricks)
22822                                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
22823                                    _ => self.write_keyword("UUID"),
22824                                }
22825                            }
22826                            // TSQL BIT -> BOOLEAN for most dialects
22827                            "BIT"
22828                                if !matches!(
22829                                    self.config.dialect,
22830                                    Some(DialectType::TSQL)
22831                                        | Some(DialectType::Fabric)
22832                                        | Some(DialectType::PostgreSQL)
22833                                        | Some(DialectType::MySQL)
22834                                        | Some(DialectType::DuckDB)
22835                                ) =>
22836                            {
22837                                self.write_keyword("BOOLEAN");
22838                            }
22839                            // TSQL NVARCHAR -> VARCHAR (with default size 30 for some dialects)
22840                            "NVARCHAR"
22841                                if !matches!(
22842                                    self.config.dialect,
22843                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
22844                                ) =>
22845                            {
22846                                match self.config.dialect {
22847                                    Some(DialectType::Oracle) => {
22848                                        // Oracle: NVARCHAR -> NVARCHAR2
22849                                        self.write_keyword("NVARCHAR2");
22850                                        if let Some(args) = _args_str {
22851                                            self.write(args);
22852                                        }
22853                                    }
22854                                    Some(DialectType::BigQuery) => {
22855                                        // BigQuery: NVARCHAR -> STRING
22856                                        self.write_keyword("STRING");
22857                                    }
22858                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
22859                                        self.write_keyword("TEXT");
22860                                        if let Some(args) = _args_str {
22861                                            self.write(args);
22862                                        }
22863                                    }
22864                                    Some(DialectType::Hive) => {
22865                                        // Hive: NVARCHAR -> STRING
22866                                        self.write_keyword("STRING");
22867                                    }
22868                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
22869                                        if _args_str.is_some() {
22870                                            self.write_keyword("VARCHAR");
22871                                            self.write(_args_str.unwrap());
22872                                        } else {
22873                                            self.write_keyword("STRING");
22874                                        }
22875                                    }
22876                                    _ => {
22877                                        self.write_keyword("VARCHAR");
22878                                        if let Some(args) = _args_str {
22879                                            self.write(args);
22880                                        }
22881                                    }
22882                                }
22883                            }
22884                            // NCHAR -> CHAR (NCHAR for Oracle/TSQL, STRING for BigQuery/Hive)
22885                            "NCHAR"
22886                                if !matches!(
22887                                    self.config.dialect,
22888                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
22889                                ) =>
22890                            {
22891                                match self.config.dialect {
22892                                    Some(DialectType::Oracle) => {
22893                                        // Oracle natively supports NCHAR
22894                                        self.write_keyword("NCHAR");
22895                                        if let Some(args) = _args_str {
22896                                            self.write(args);
22897                                        }
22898                                    }
22899                                    Some(DialectType::BigQuery) => {
22900                                        // BigQuery: NCHAR -> STRING
22901                                        self.write_keyword("STRING");
22902                                    }
22903                                    Some(DialectType::Hive) => {
22904                                        // Hive: NCHAR -> STRING
22905                                        self.write_keyword("STRING");
22906                                    }
22907                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
22908                                        self.write_keyword("TEXT");
22909                                        if let Some(args) = _args_str {
22910                                            self.write(args);
22911                                        }
22912                                    }
22913                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
22914                                        if _args_str.is_some() {
22915                                            self.write_keyword("CHAR");
22916                                            self.write(_args_str.unwrap());
22917                                        } else {
22918                                            self.write_keyword("STRING");
22919                                        }
22920                                    }
22921                                    _ => {
22922                                        self.write_keyword("CHAR");
22923                                        if let Some(args) = _args_str {
22924                                            self.write(args);
22925                                        }
22926                                    }
22927                                }
22928                            }
22929                            // MySQL text variant types -> map to appropriate target type
22930                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
22931                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => match self.config.dialect {
22932                                Some(DialectType::MySQL)
22933                                | Some(DialectType::SingleStore)
22934                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
22935                                Some(DialectType::Spark)
22936                                | Some(DialectType::Databricks)
22937                                | Some(DialectType::Hive) => self.write_keyword("TEXT"),
22938                                Some(DialectType::BigQuery) => self.write_keyword("STRING"),
22939                                Some(DialectType::Presto)
22940                                | Some(DialectType::Trino)
22941                                | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
22942                                Some(DialectType::Snowflake)
22943                                | Some(DialectType::Redshift)
22944                                | Some(DialectType::Dremio) => self.write_keyword("VARCHAR"),
22945                                _ => self.write_keyword("TEXT"),
22946                            },
22947                            // MySQL blob variant types -> map to appropriate target type
22948                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
22949                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => match self.config.dialect {
22950                                Some(DialectType::MySQL)
22951                                | Some(DialectType::SingleStore)
22952                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
22953                                Some(DialectType::Spark)
22954                                | Some(DialectType::Databricks)
22955                                | Some(DialectType::Hive) => self.write_keyword("BLOB"),
22956                                Some(DialectType::DuckDB) => self.write_keyword("VARBINARY"),
22957                                Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
22958                                Some(DialectType::Presto)
22959                                | Some(DialectType::Trino)
22960                                | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
22961                                Some(DialectType::Snowflake)
22962                                | Some(DialectType::Redshift)
22963                                | Some(DialectType::Dremio) => self.write_keyword("VARBINARY"),
22964                                _ => self.write_keyword("BLOB"),
22965                            },
22966                            // LONGVARCHAR -> TEXT for SQLite, VARCHAR for others
22967                            "LONGVARCHAR" => match self.config.dialect {
22968                                Some(DialectType::SQLite) => self.write_keyword("TEXT"),
22969                                _ => self.write_keyword("VARCHAR"),
22970                            },
22971                            // DATETIME -> TIMESTAMP for most, DATETIME for MySQL/Doris/StarRocks/Snowflake
22972                            "DATETIME" => {
22973                                match self.config.dialect {
22974                                    Some(DialectType::MySQL)
22975                                    | Some(DialectType::Doris)
22976                                    | Some(DialectType::StarRocks)
22977                                    | Some(DialectType::TSQL)
22978                                    | Some(DialectType::Fabric)
22979                                    | Some(DialectType::BigQuery)
22980                                    | Some(DialectType::SQLite)
22981                                    | Some(DialectType::Snowflake) => {
22982                                        self.write_keyword("DATETIME");
22983                                        if let Some(args) = _args_str {
22984                                            self.write(args);
22985                                        }
22986                                    }
22987                                    Some(_) => {
22988                                        // Only map to TIMESTAMP when we have a specific target dialect
22989                                        self.write_keyword("TIMESTAMP");
22990                                        if let Some(args) = _args_str {
22991                                            self.write(args);
22992                                        }
22993                                    }
22994                                    None => {
22995                                        // No dialect - preserve original
22996                                        self.write(name);
22997                                    }
22998                                }
22999                            }
23000                            // VARCHAR2/NVARCHAR2 (Oracle) -> VARCHAR for non-Oracle targets
23001                            "VARCHAR2"
23002                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
23003                            {
23004                                match self.config.dialect {
23005                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
23006                                        self.write_keyword("TEXT");
23007                                    }
23008                                    Some(DialectType::Hive)
23009                                    | Some(DialectType::Spark)
23010                                    | Some(DialectType::Databricks)
23011                                    | Some(DialectType::BigQuery)
23012                                    | Some(DialectType::ClickHouse)
23013                                    | Some(DialectType::StarRocks)
23014                                    | Some(DialectType::Doris) => {
23015                                        self.write_keyword("STRING");
23016                                    }
23017                                    _ => {
23018                                        self.write_keyword("VARCHAR");
23019                                        if let Some(args) = _args_str {
23020                                            self.write(args);
23021                                        }
23022                                    }
23023                                }
23024                            }
23025                            "NVARCHAR2"
23026                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
23027                            {
23028                                match self.config.dialect {
23029                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
23030                                        self.write_keyword("TEXT");
23031                                    }
23032                                    Some(DialectType::Hive)
23033                                    | Some(DialectType::Spark)
23034                                    | Some(DialectType::Databricks)
23035                                    | Some(DialectType::BigQuery)
23036                                    | Some(DialectType::ClickHouse)
23037                                    | Some(DialectType::StarRocks)
23038                                    | Some(DialectType::Doris) => {
23039                                        self.write_keyword("STRING");
23040                                    }
23041                                    _ => {
23042                                        self.write_keyword("VARCHAR");
23043                                        if let Some(args) = _args_str {
23044                                            self.write(args);
23045                                        }
23046                                    }
23047                                }
23048                            }
23049                            _ => self.write(name),
23050                        }
23051                    }
23052                }
23053            }
23054            DataType::Geometry { subtype, srid } => {
23055                // Dialect-specific geometry type mappings
23056                match self.config.dialect {
23057                    Some(DialectType::MySQL) => {
23058                        // MySQL uses POINT SRID 4326 syntax for specific types
23059                        if let Some(sub) = subtype {
23060                            self.write_keyword(sub);
23061                            if let Some(s) = srid {
23062                                self.write(" SRID ");
23063                                self.write(&s.to_string());
23064                            }
23065                        } else {
23066                            self.write_keyword("GEOMETRY");
23067                        }
23068                    }
23069                    Some(DialectType::BigQuery) => {
23070                        // BigQuery only supports GEOGRAPHY, not GEOMETRY
23071                        self.write_keyword("GEOGRAPHY");
23072                    }
23073                    Some(DialectType::Teradata) => {
23074                        // Teradata uses ST_GEOMETRY
23075                        self.write_keyword("ST_GEOMETRY");
23076                        if subtype.is_some() || srid.is_some() {
23077                            self.write("(");
23078                            if let Some(sub) = subtype {
23079                                self.write_keyword(sub);
23080                            }
23081                            if let Some(s) = srid {
23082                                if subtype.is_some() {
23083                                    self.write(", ");
23084                                }
23085                                self.write(&s.to_string());
23086                            }
23087                            self.write(")");
23088                        }
23089                    }
23090                    _ => {
23091                        // PostgreSQL, Snowflake, DuckDB use GEOMETRY(subtype, srid) syntax
23092                        self.write_keyword("GEOMETRY");
23093                        if subtype.is_some() || srid.is_some() {
23094                            self.write("(");
23095                            if let Some(sub) = subtype {
23096                                self.write_keyword(sub);
23097                            }
23098                            if let Some(s) = srid {
23099                                if subtype.is_some() {
23100                                    self.write(", ");
23101                                }
23102                                self.write(&s.to_string());
23103                            }
23104                            self.write(")");
23105                        }
23106                    }
23107                }
23108            }
23109            DataType::Geography { subtype, srid } => {
23110                // Dialect-specific geography type mappings
23111                match self.config.dialect {
23112                    Some(DialectType::MySQL) => {
23113                        // MySQL doesn't have native GEOGRAPHY, use GEOMETRY with SRID 4326
23114                        if let Some(sub) = subtype {
23115                            self.write_keyword(sub);
23116                        } else {
23117                            self.write_keyword("GEOMETRY");
23118                        }
23119                        // Geography implies SRID 4326 (WGS84)
23120                        let effective_srid = srid.unwrap_or(4326);
23121                        self.write(" SRID ");
23122                        self.write(&effective_srid.to_string());
23123                    }
23124                    Some(DialectType::BigQuery) => {
23125                        // BigQuery uses simple GEOGRAPHY without parameters
23126                        self.write_keyword("GEOGRAPHY");
23127                    }
23128                    Some(DialectType::Snowflake) => {
23129                        // Snowflake uses GEOGRAPHY without parameters
23130                        self.write_keyword("GEOGRAPHY");
23131                    }
23132                    _ => {
23133                        // PostgreSQL uses GEOGRAPHY(subtype, srid) syntax
23134                        self.write_keyword("GEOGRAPHY");
23135                        if subtype.is_some() || srid.is_some() {
23136                            self.write("(");
23137                            if let Some(sub) = subtype {
23138                                self.write_keyword(sub);
23139                            }
23140                            if let Some(s) = srid {
23141                                if subtype.is_some() {
23142                                    self.write(", ");
23143                                }
23144                                self.write(&s.to_string());
23145                            }
23146                            self.write(")");
23147                        }
23148                    }
23149                }
23150            }
23151            DataType::CharacterSet { name } => {
23152                // For MySQL CONVERT USING - output as CHAR CHARACTER SET name
23153                self.write_keyword("CHAR CHARACTER SET ");
23154                self.write(name);
23155            }
23156            _ => self.write("UNKNOWN"),
23157        }
23158        Ok(())
23159    }
23160
23161    // === Helper methods ===
23162
23163    fn write(&mut self, s: &str) {
23164        self.output.push_str(s);
23165    }
23166
23167    fn write_space(&mut self) {
23168        self.output.push(' ');
23169    }
23170
23171    fn write_keyword(&mut self, keyword: &str) {
23172        if self.config.uppercase_keywords {
23173            self.output.push_str(keyword);
23174        } else {
23175            self.output.push_str(&keyword.to_lowercase());
23176        }
23177    }
23178
23179    /// Write a function name respecting the normalize_functions config setting
23180    fn write_func_name(&mut self, name: &str) {
23181        let normalized = self.normalize_func_name(name);
23182        self.output.push_str(&normalized);
23183    }
23184
23185    /// Convert strptime format string to Exasol format string
23186    /// Exasol TIME_MAPPING (reverse of Python sqlglot):
23187    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH, %M -> MI, %S -> SS, %a -> DY
23188    fn convert_strptime_to_exasol_format(format: &str) -> String {
23189        let mut result = String::new();
23190        let chars: Vec<char> = format.chars().collect();
23191        let mut i = 0;
23192        while i < chars.len() {
23193            if chars[i] == '%' && i + 1 < chars.len() {
23194                let spec = chars[i + 1];
23195                let exasol_spec = match spec {
23196                    'Y' => "YYYY",
23197                    'y' => "YY",
23198                    'm' => "MM",
23199                    'd' => "DD",
23200                    'H' => "HH",
23201                    'M' => "MI",
23202                    'S' => "SS",
23203                    'a' => "DY",    // abbreviated weekday name
23204                    'A' => "DAY",   // full weekday name
23205                    'b' => "MON",   // abbreviated month name
23206                    'B' => "MONTH", // full month name
23207                    'I' => "H12",   // 12-hour format
23208                    'u' => "ID",    // ISO weekday (1-7)
23209                    'V' => "IW",    // ISO week number
23210                    'G' => "IYYY",  // ISO year
23211                    'W' => "UW",    // Week number (Monday as first day)
23212                    'U' => "UW",    // Week number (Sunday as first day)
23213                    'z' => "Z",     // timezone offset
23214                    _ => {
23215                        // Unknown specifier, keep as-is
23216                        result.push('%');
23217                        result.push(spec);
23218                        i += 2;
23219                        continue;
23220                    }
23221                };
23222                result.push_str(exasol_spec);
23223                i += 2;
23224            } else {
23225                result.push(chars[i]);
23226                i += 1;
23227            }
23228        }
23229        result
23230    }
23231
23232    /// Convert strptime format string to PostgreSQL/Redshift format string
23233    /// PostgreSQL INVERSE_TIME_MAPPING from Python sqlglot:
23234    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH24, %M -> MI, %S -> SS, %f -> US, etc.
23235    fn convert_strptime_to_postgres_format(format: &str) -> String {
23236        let mut result = String::new();
23237        let chars: Vec<char> = format.chars().collect();
23238        let mut i = 0;
23239        while i < chars.len() {
23240            if chars[i] == '%' && i + 1 < chars.len() {
23241                // Check for %-d, %-m, etc. (non-padded, 3-char sequence)
23242                if chars[i + 1] == '-' && i + 2 < chars.len() {
23243                    let spec = chars[i + 2];
23244                    let pg_spec = match spec {
23245                        'd' => "FMDD",
23246                        'm' => "FMMM",
23247                        'H' => "FMHH24",
23248                        'M' => "FMMI",
23249                        'S' => "FMSS",
23250                        _ => {
23251                            result.push('%');
23252                            result.push('-');
23253                            result.push(spec);
23254                            i += 3;
23255                            continue;
23256                        }
23257                    };
23258                    result.push_str(pg_spec);
23259                    i += 3;
23260                    continue;
23261                }
23262                let spec = chars[i + 1];
23263                let pg_spec = match spec {
23264                    'Y' => "YYYY",
23265                    'y' => "YY",
23266                    'm' => "MM",
23267                    'd' => "DD",
23268                    'H' => "HH24",
23269                    'I' => "HH12",
23270                    'M' => "MI",
23271                    'S' => "SS",
23272                    'f' => "US",      // microseconds
23273                    'u' => "D",       // day of week (1=Monday)
23274                    'j' => "DDD",     // day of year
23275                    'z' => "OF",      // UTC offset
23276                    'Z' => "TZ",      // timezone name
23277                    'A' => "TMDay",   // full weekday name
23278                    'a' => "TMDy",    // abbreviated weekday name
23279                    'b' => "TMMon",   // abbreviated month name
23280                    'B' => "TMMonth", // full month name
23281                    'U' => "WW",      // week number
23282                    _ => {
23283                        // Unknown specifier, keep as-is
23284                        result.push('%');
23285                        result.push(spec);
23286                        i += 2;
23287                        continue;
23288                    }
23289                };
23290                result.push_str(pg_spec);
23291                i += 2;
23292            } else {
23293                result.push(chars[i]);
23294                i += 1;
23295            }
23296        }
23297        result
23298    }
23299
23300    /// Write a LIMIT expression value, evaluating constant expressions if limit_only_literals is set
23301    fn write_limit_expr(&mut self, expr: &Expression) -> Result<()> {
23302        if self.config.limit_only_literals {
23303            if let Some(value) = Self::try_evaluate_constant(expr) {
23304                self.write(&value.to_string());
23305                return Ok(());
23306            }
23307        }
23308        self.generate_expression(expr)
23309    }
23310
23311    /// Format a comment with proper spacing.
23312    /// Converts `/*text*/` to `/* text */` (adding internal spaces if not present).
23313    /// Python SQLGlot normalizes comment format to have spaces inside block comments.
23314    fn write_formatted_comment(&mut self, comment: &str) {
23315        // Normalize all comments to block comment format /* ... */
23316        // This matches Python sqlglot behavior which always outputs block comments
23317        let content = if comment.starts_with("/*") && comment.ends_with("*/") {
23318            // Already block comment - extract inner content
23319            // Preserve internal whitespace, but ensure at least one space padding
23320            &comment[2..comment.len() - 2]
23321        } else if comment.starts_with("--") {
23322            // Line comment - extract content after --
23323            // Preserve internal whitespace (e.g., "--       x" -> "/*       x */")
23324            &comment[2..]
23325        } else {
23326            // Raw content (no delimiters)
23327            comment
23328        };
23329        // Skip empty comments (e.g., bare "--" with no content)
23330        if content.trim().is_empty() {
23331            return;
23332        }
23333        // Ensure at least one space after /* and before */
23334        self.output.push_str("/*");
23335        if !content.starts_with(' ') {
23336            self.output.push(' ');
23337        }
23338        self.output.push_str(content);
23339        if !content.ends_with(' ') {
23340            self.output.push(' ');
23341        }
23342        self.output.push_str("*/");
23343    }
23344
23345    /// Escape a raw block content (from dollar-quoted string) for single-quoted output.
23346    /// Escapes single quotes with backslash, and for Snowflake also escapes backslashes.
23347    fn escape_block_for_single_quote(&self, block: &str) -> String {
23348        let escape_backslash = matches!(
23349            self.config.dialect,
23350            Some(crate::dialects::DialectType::Snowflake)
23351        );
23352        let mut escaped = String::with_capacity(block.len() + 4);
23353        for ch in block.chars() {
23354            if ch == '\'' {
23355                escaped.push('\\');
23356                escaped.push('\'');
23357            } else if escape_backslash && ch == '\\' {
23358                escaped.push('\\');
23359                escaped.push('\\');
23360            } else {
23361                escaped.push(ch);
23362            }
23363        }
23364        escaped
23365    }
23366
23367    fn write_newline(&mut self) {
23368        self.output.push('\n');
23369    }
23370
23371    fn write_indent(&mut self) {
23372        for _ in 0..self.indent_level {
23373            self.output.push_str(&self.config.indent);
23374        }
23375    }
23376
23377    // === SQLGlot-style pretty printing helpers ===
23378
23379    /// Returns the separator string for pretty printing.
23380    /// Check if the total length of arguments exceeds max_text_width.
23381    /// Used for dynamic line breaking in expressions() formatting.
23382    fn too_wide(&self, args: &[String]) -> bool {
23383        args.iter().map(|s| s.len()).sum::<usize>() > self.config.max_text_width
23384    }
23385
23386    /// Generate an expression to a string using a temporary non-pretty generator.
23387    /// Useful for width calculations before deciding on formatting.
23388    fn generate_to_string(&self, expr: &Expression) -> Result<String> {
23389        let config = GeneratorConfig {
23390            pretty: false,
23391            dialect: self.config.dialect,
23392            ..Default::default()
23393        };
23394        let mut gen = Generator::with_config(config);
23395        gen.generate_expression(expr)?;
23396        Ok(gen.output)
23397    }
23398
23399    /// Writes a clause with a single condition (WHERE, HAVING, QUALIFY).
23400    /// In pretty mode: newline + indented keyword + newline + indented condition
23401    fn write_clause_condition(&mut self, keyword: &str, condition: &Expression) -> Result<()> {
23402        if self.config.pretty {
23403            self.write_newline();
23404            self.write_indent();
23405            self.write_keyword(keyword);
23406            self.write_newline();
23407            self.indent_level += 1;
23408            self.write_indent();
23409            self.generate_expression(condition)?;
23410            self.indent_level -= 1;
23411        } else {
23412            self.write_space();
23413            self.write_keyword(keyword);
23414            self.write_space();
23415            self.generate_expression(condition)?;
23416        }
23417        Ok(())
23418    }
23419
23420    /// Writes a clause with a list of expressions (GROUP BY, DISTRIBUTE BY, CLUSTER BY).
23421    /// In pretty mode: each expression on new line with indentation
23422    fn write_clause_expressions(&mut self, keyword: &str, exprs: &[Expression]) -> Result<()> {
23423        if exprs.is_empty() {
23424            return Ok(());
23425        }
23426
23427        if self.config.pretty {
23428            self.write_newline();
23429            self.write_indent();
23430            self.write_keyword(keyword);
23431            self.write_newline();
23432            self.indent_level += 1;
23433            for (i, expr) in exprs.iter().enumerate() {
23434                if i > 0 {
23435                    self.write(",");
23436                    self.write_newline();
23437                }
23438                self.write_indent();
23439                self.generate_expression(expr)?;
23440            }
23441            self.indent_level -= 1;
23442        } else {
23443            self.write_space();
23444            self.write_keyword(keyword);
23445            self.write_space();
23446            for (i, expr) in exprs.iter().enumerate() {
23447                if i > 0 {
23448                    self.write(", ");
23449                }
23450                self.generate_expression(expr)?;
23451            }
23452        }
23453        Ok(())
23454    }
23455
23456    /// Writes ORDER BY / SORT BY clause with Ordered expressions
23457    fn write_order_clause(&mut self, keyword: &str, orderings: &[Ordered]) -> Result<()> {
23458        if orderings.is_empty() {
23459            return Ok(());
23460        }
23461
23462        if self.config.pretty {
23463            self.write_newline();
23464            self.write_indent();
23465            self.write_keyword(keyword);
23466            self.write_newline();
23467            self.indent_level += 1;
23468            for (i, ordered) in orderings.iter().enumerate() {
23469                if i > 0 {
23470                    self.write(",");
23471                    self.write_newline();
23472                }
23473                self.write_indent();
23474                self.generate_ordered(ordered)?;
23475            }
23476            self.indent_level -= 1;
23477        } else {
23478            self.write_space();
23479            self.write_keyword(keyword);
23480            self.write_space();
23481            for (i, ordered) in orderings.iter().enumerate() {
23482                if i > 0 {
23483                    self.write(", ");
23484                }
23485                self.generate_ordered(ordered)?;
23486            }
23487        }
23488        Ok(())
23489    }
23490
23491    /// Writes WINDOW clause with named window definitions
23492    fn write_window_clause(&mut self, windows: &[NamedWindow]) -> Result<()> {
23493        if windows.is_empty() {
23494            return Ok(());
23495        }
23496
23497        if self.config.pretty {
23498            self.write_newline();
23499            self.write_indent();
23500            self.write_keyword("WINDOW");
23501            self.write_newline();
23502            self.indent_level += 1;
23503            for (i, named_window) in windows.iter().enumerate() {
23504                if i > 0 {
23505                    self.write(",");
23506                    self.write_newline();
23507                }
23508                self.write_indent();
23509                self.generate_identifier(&named_window.name)?;
23510                self.write_space();
23511                self.write_keyword("AS");
23512                self.write(" (");
23513                self.generate_over(&named_window.spec)?;
23514                self.write(")");
23515            }
23516            self.indent_level -= 1;
23517        } else {
23518            self.write_space();
23519            self.write_keyword("WINDOW");
23520            self.write_space();
23521            for (i, named_window) in windows.iter().enumerate() {
23522                if i > 0 {
23523                    self.write(", ");
23524                }
23525                self.generate_identifier(&named_window.name)?;
23526                self.write_space();
23527                self.write_keyword("AS");
23528                self.write(" (");
23529                self.generate_over(&named_window.spec)?;
23530                self.write(")");
23531            }
23532        }
23533        Ok(())
23534    }
23535
23536    // === BATCH-GENERATED STUB METHODS (481 variants) ===
23537    fn generate_ai_agg(&mut self, e: &AIAgg) -> Result<()> {
23538        // AI_AGG(this, expression)
23539        self.write_keyword("AI_AGG");
23540        self.write("(");
23541        self.generate_expression(&e.this)?;
23542        self.write(", ");
23543        self.generate_expression(&e.expression)?;
23544        self.write(")");
23545        Ok(())
23546    }
23547
23548    fn generate_ai_classify(&mut self, e: &AIClassify) -> Result<()> {
23549        // AI_CLASSIFY(input, [categories], [config])
23550        self.write_keyword("AI_CLASSIFY");
23551        self.write("(");
23552        self.generate_expression(&e.this)?;
23553        if let Some(categories) = &e.categories {
23554            self.write(", ");
23555            self.generate_expression(categories)?;
23556        }
23557        if let Some(config) = &e.config {
23558            self.write(", ");
23559            self.generate_expression(config)?;
23560        }
23561        self.write(")");
23562        Ok(())
23563    }
23564
23565    fn generate_add_partition(&mut self, e: &AddPartition) -> Result<()> {
23566        // Python: return f"ADD {exists}{self.sql(expression.this)}{location}"
23567        self.write_keyword("ADD");
23568        self.write_space();
23569        if e.exists {
23570            self.write_keyword("IF NOT EXISTS");
23571            self.write_space();
23572        }
23573        self.generate_expression(&e.this)?;
23574        if let Some(location) = &e.location {
23575            self.write_space();
23576            self.generate_expression(location)?;
23577        }
23578        Ok(())
23579    }
23580
23581    fn generate_algorithm_property(&mut self, e: &AlgorithmProperty) -> Result<()> {
23582        // Python: return f"ALGORITHM={self.sql(expression, 'this')}"
23583        self.write_keyword("ALGORITHM");
23584        self.write("=");
23585        self.generate_expression(&e.this)?;
23586        Ok(())
23587    }
23588
23589    fn generate_aliases(&mut self, e: &Aliases) -> Result<()> {
23590        // Python: return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
23591        self.generate_expression(&e.this)?;
23592        self.write_space();
23593        self.write_keyword("AS");
23594        self.write(" (");
23595        for (i, expr) in e.expressions.iter().enumerate() {
23596            if i > 0 {
23597                self.write(", ");
23598            }
23599            self.generate_expression(expr)?;
23600        }
23601        self.write(")");
23602        Ok(())
23603    }
23604
23605    fn generate_allowed_values_property(&mut self, e: &AllowedValuesProperty) -> Result<()> {
23606        // Python: return f"ALLOWED_VALUES {self.expressions(e, flat=True)}"
23607        self.write_keyword("ALLOWED_VALUES");
23608        self.write_space();
23609        for (i, expr) in e.expressions.iter().enumerate() {
23610            if i > 0 {
23611                self.write(", ");
23612            }
23613            self.generate_expression(expr)?;
23614        }
23615        Ok(())
23616    }
23617
23618    fn generate_alter_column(&mut self, e: &AlterColumn) -> Result<()> {
23619        // Python: complex logic based on dtype, default, comment, visible, etc.
23620        self.write_keyword("ALTER COLUMN");
23621        self.write_space();
23622        self.generate_expression(&e.this)?;
23623
23624        if let Some(dtype) = &e.dtype {
23625            self.write_space();
23626            self.write_keyword("SET DATA TYPE");
23627            self.write_space();
23628            self.generate_expression(dtype)?;
23629            if let Some(collate) = &e.collate {
23630                self.write_space();
23631                self.write_keyword("COLLATE");
23632                self.write_space();
23633                self.generate_expression(collate)?;
23634            }
23635            if let Some(using) = &e.using {
23636                self.write_space();
23637                self.write_keyword("USING");
23638                self.write_space();
23639                self.generate_expression(using)?;
23640            }
23641        } else if let Some(default) = &e.default {
23642            self.write_space();
23643            self.write_keyword("SET DEFAULT");
23644            self.write_space();
23645            self.generate_expression(default)?;
23646        } else if let Some(comment) = &e.comment {
23647            self.write_space();
23648            self.write_keyword("COMMENT");
23649            self.write_space();
23650            self.generate_expression(comment)?;
23651        } else if let Some(drop) = &e.drop {
23652            self.write_space();
23653            self.write_keyword("DROP");
23654            self.write_space();
23655            self.generate_expression(drop)?;
23656        } else if let Some(visible) = &e.visible {
23657            self.write_space();
23658            self.generate_expression(visible)?;
23659        } else if let Some(rename_to) = &e.rename_to {
23660            self.write_space();
23661            self.write_keyword("RENAME TO");
23662            self.write_space();
23663            self.generate_expression(rename_to)?;
23664        } else if let Some(allow_null) = &e.allow_null {
23665            self.write_space();
23666            self.generate_expression(allow_null)?;
23667        }
23668        Ok(())
23669    }
23670
23671    fn generate_alter_session(&mut self, e: &AlterSession) -> Result<()> {
23672        // Python: keyword = "UNSET" if expression.args.get("unset") else "SET"; return f"{keyword} {items_sql}"
23673        self.write_keyword("ALTER SESSION");
23674        self.write_space();
23675        if e.unset.is_some() {
23676            self.write_keyword("UNSET");
23677        } else {
23678            self.write_keyword("SET");
23679        }
23680        self.write_space();
23681        for (i, expr) in e.expressions.iter().enumerate() {
23682            if i > 0 {
23683                self.write(", ");
23684            }
23685            self.generate_expression(expr)?;
23686        }
23687        Ok(())
23688    }
23689
23690    fn generate_alter_set(&mut self, e: &AlterSet) -> Result<()> {
23691        // Python (Snowflake): return f"SET{exprs}{file_format}{copy_options}{tag}"
23692        self.write_keyword("SET");
23693
23694        // Generate option (e.g., AUTHORIZATION, LOGGED, UNLOGGED, etc.)
23695        if let Some(opt) = &e.option {
23696            self.write_space();
23697            self.generate_expression(opt)?;
23698        }
23699
23700        // Generate PROPERTIES (for Trino SET PROPERTIES x = y, ...)
23701        // Check if expressions look like property assignments
23702        if !e.expressions.is_empty() {
23703            // Check if this looks like property assignments (for SET PROPERTIES)
23704            let is_properties = e
23705                .expressions
23706                .iter()
23707                .any(|expr| matches!(expr, Expression::Eq(_)));
23708            if is_properties && e.option.is_none() {
23709                self.write_space();
23710                self.write_keyword("PROPERTIES");
23711            }
23712            self.write_space();
23713            for (i, expr) in e.expressions.iter().enumerate() {
23714                if i > 0 {
23715                    self.write(", ");
23716                }
23717                self.generate_expression(expr)?;
23718            }
23719        }
23720
23721        // Generate STAGE_FILE_FORMAT = (...) with space-separated properties
23722        if let Some(file_format) = &e.file_format {
23723            self.write(" ");
23724            self.write_keyword("STAGE_FILE_FORMAT");
23725            self.write(" = (");
23726            self.generate_space_separated_properties(file_format)?;
23727            self.write(")");
23728        }
23729
23730        // Generate STAGE_COPY_OPTIONS = (...) with space-separated properties
23731        if let Some(copy_options) = &e.copy_options {
23732            self.write(" ");
23733            self.write_keyword("STAGE_COPY_OPTIONS");
23734            self.write(" = (");
23735            self.generate_space_separated_properties(copy_options)?;
23736            self.write(")");
23737        }
23738
23739        // Generate TAG ...
23740        if let Some(tag) = &e.tag {
23741            self.write(" ");
23742            self.write_keyword("TAG");
23743            self.write(" ");
23744            self.generate_expression(tag)?;
23745        }
23746
23747        Ok(())
23748    }
23749
23750    /// Generate space-separated properties (for Snowflake STAGE_FILE_FORMAT, etc.)
23751    fn generate_space_separated_properties(&mut self, expr: &Expression) -> Result<()> {
23752        match expr {
23753            Expression::Tuple(t) => {
23754                for (i, prop) in t.expressions.iter().enumerate() {
23755                    if i > 0 {
23756                        self.write(" ");
23757                    }
23758                    self.generate_expression(prop)?;
23759                }
23760            }
23761            _ => {
23762                self.generate_expression(expr)?;
23763            }
23764        }
23765        Ok(())
23766    }
23767
23768    fn generate_alter_sort_key(&mut self, e: &AlterSortKey) -> Result<()> {
23769        // Python: return f"ALTER{compound} SORTKEY {this or expressions}"
23770        self.write_keyword("ALTER");
23771        if e.compound.is_some() {
23772            self.write_space();
23773            self.write_keyword("COMPOUND");
23774        }
23775        self.write_space();
23776        self.write_keyword("SORTKEY");
23777        self.write_space();
23778        if let Some(this) = &e.this {
23779            self.generate_expression(this)?;
23780        } else if !e.expressions.is_empty() {
23781            self.write("(");
23782            for (i, expr) in e.expressions.iter().enumerate() {
23783                if i > 0 {
23784                    self.write(", ");
23785                }
23786                self.generate_expression(expr)?;
23787            }
23788            self.write(")");
23789        }
23790        Ok(())
23791    }
23792
23793    fn generate_analyze(&mut self, e: &Analyze) -> Result<()> {
23794        // Python: return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
23795        self.write_keyword("ANALYZE");
23796        if !e.options.is_empty() {
23797            self.write_space();
23798            for (i, opt) in e.options.iter().enumerate() {
23799                if i > 0 {
23800                    self.write_space();
23801                }
23802                // Write options as keywords (not identifiers) to avoid quoting reserved words like FULL
23803                if let Expression::Identifier(id) = opt {
23804                    self.write_keyword(&id.name);
23805                } else {
23806                    self.generate_expression(opt)?;
23807                }
23808            }
23809        }
23810        if let Some(kind) = &e.kind {
23811            self.write_space();
23812            self.write_keyword(kind);
23813        }
23814        if let Some(this) = &e.this {
23815            self.write_space();
23816            self.generate_expression(this)?;
23817        }
23818        // Column list: ANALYZE tbl(col1, col2) (PostgreSQL)
23819        if !e.columns.is_empty() {
23820            self.write("(");
23821            for (i, col) in e.columns.iter().enumerate() {
23822                if i > 0 {
23823                    self.write(", ");
23824                }
23825                self.write(col);
23826            }
23827            self.write(")");
23828        }
23829        if let Some(partition) = &e.partition {
23830            self.write_space();
23831            self.generate_expression(partition)?;
23832        }
23833        if let Some(mode) = &e.mode {
23834            self.write_space();
23835            self.generate_expression(mode)?;
23836        }
23837        if let Some(expression) = &e.expression {
23838            self.write_space();
23839            self.generate_expression(expression)?;
23840        }
23841        if !e.properties.is_empty() {
23842            self.write_space();
23843            self.write_keyword(self.config.with_properties_prefix);
23844            self.write(" (");
23845            for (i, prop) in e.properties.iter().enumerate() {
23846                if i > 0 {
23847                    self.write(", ");
23848                }
23849                self.generate_expression(prop)?;
23850            }
23851            self.write(")");
23852        }
23853        Ok(())
23854    }
23855
23856    fn generate_analyze_delete(&mut self, e: &AnalyzeDelete) -> Result<()> {
23857        // Python: return f"DELETE{kind} STATISTICS"
23858        self.write_keyword("DELETE");
23859        if let Some(kind) = &e.kind {
23860            self.write_space();
23861            self.write_keyword(kind);
23862        }
23863        self.write_space();
23864        self.write_keyword("STATISTICS");
23865        Ok(())
23866    }
23867
23868    fn generate_analyze_histogram(&mut self, e: &AnalyzeHistogram) -> Result<()> {
23869        // Python: return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
23870        // Write `this` (UPDATE or DROP) as keyword to avoid quoting reserved words
23871        if let Expression::Identifier(id) = e.this.as_ref() {
23872            self.write_keyword(&id.name);
23873        } else {
23874            self.generate_expression(&e.this)?;
23875        }
23876        self.write_space();
23877        self.write_keyword("HISTOGRAM ON");
23878        self.write_space();
23879        for (i, expr) in e.expressions.iter().enumerate() {
23880            if i > 0 {
23881                self.write(", ");
23882            }
23883            self.generate_expression(expr)?;
23884        }
23885        if let Some(expression) = &e.expression {
23886            self.write_space();
23887            self.generate_expression(expression)?;
23888        }
23889        if let Some(update_options) = &e.update_options {
23890            self.write_space();
23891            self.generate_expression(update_options)?;
23892            self.write_space();
23893            self.write_keyword("UPDATE");
23894        }
23895        Ok(())
23896    }
23897
23898    fn generate_analyze_list_chained_rows(&mut self, e: &AnalyzeListChainedRows) -> Result<()> {
23899        // Python: return f"LIST CHAINED ROWS{inner_expression}"
23900        self.write_keyword("LIST CHAINED ROWS");
23901        if let Some(expression) = &e.expression {
23902            self.write_space();
23903            self.write_keyword("INTO");
23904            self.write_space();
23905            self.generate_expression(expression)?;
23906        }
23907        Ok(())
23908    }
23909
23910    fn generate_analyze_sample(&mut self, e: &AnalyzeSample) -> Result<()> {
23911        // Python: return f"SAMPLE {sample} {kind}"
23912        self.write_keyword("SAMPLE");
23913        self.write_space();
23914        if let Some(sample) = &e.sample {
23915            self.generate_expression(sample)?;
23916            self.write_space();
23917        }
23918        self.write_keyword(&e.kind);
23919        Ok(())
23920    }
23921
23922    fn generate_analyze_statistics(&mut self, e: &AnalyzeStatistics) -> Result<()> {
23923        // Python: return f"{kind}{option} STATISTICS{this}{columns}"
23924        self.write_keyword(&e.kind);
23925        if let Some(option) = &e.option {
23926            self.write_space();
23927            self.generate_expression(option)?;
23928        }
23929        self.write_space();
23930        self.write_keyword("STATISTICS");
23931        if let Some(this) = &e.this {
23932            self.write_space();
23933            self.generate_expression(this)?;
23934        }
23935        if !e.expressions.is_empty() {
23936            self.write_space();
23937            for (i, expr) in e.expressions.iter().enumerate() {
23938                if i > 0 {
23939                    self.write(", ");
23940                }
23941                self.generate_expression(expr)?;
23942            }
23943        }
23944        Ok(())
23945    }
23946
23947    fn generate_analyze_validate(&mut self, e: &AnalyzeValidate) -> Result<()> {
23948        // Python: return f"VALIDATE {kind}{this}{inner_expression}"
23949        self.write_keyword("VALIDATE");
23950        self.write_space();
23951        self.write_keyword(&e.kind);
23952        if let Some(this) = &e.this {
23953            self.write_space();
23954            // this is a keyword string like "UPDATE", "CASCADE FAST", etc. - write as keywords
23955            if let Expression::Identifier(id) = this.as_ref() {
23956                self.write_keyword(&id.name);
23957            } else {
23958                self.generate_expression(this)?;
23959            }
23960        }
23961        if let Some(expression) = &e.expression {
23962            self.write_space();
23963            self.write_keyword("INTO");
23964            self.write_space();
23965            self.generate_expression(expression)?;
23966        }
23967        Ok(())
23968    }
23969
23970    fn generate_analyze_with(&mut self, e: &AnalyzeWith) -> Result<()> {
23971        // Python: return f"WITH {expressions}"
23972        self.write_keyword("WITH");
23973        self.write_space();
23974        for (i, expr) in e.expressions.iter().enumerate() {
23975            if i > 0 {
23976                self.write(", ");
23977            }
23978            self.generate_expression(expr)?;
23979        }
23980        Ok(())
23981    }
23982
23983    fn generate_anonymous(&mut self, e: &Anonymous) -> Result<()> {
23984        // Anonymous represents a generic function call: FUNC_NAME(args...)
23985        // Python: return self.func(self.sql(expression, "this"), *expression.expressions)
23986        self.generate_expression(&e.this)?;
23987        self.write("(");
23988        for (i, arg) in e.expressions.iter().enumerate() {
23989            if i > 0 {
23990                self.write(", ");
23991            }
23992            self.generate_expression(arg)?;
23993        }
23994        self.write(")");
23995        Ok(())
23996    }
23997
23998    fn generate_anonymous_agg_func(&mut self, e: &AnonymousAggFunc) -> Result<()> {
23999        // Same as Anonymous but for aggregate functions
24000        self.generate_expression(&e.this)?;
24001        self.write("(");
24002        for (i, arg) in e.expressions.iter().enumerate() {
24003            if i > 0 {
24004                self.write(", ");
24005            }
24006            self.generate_expression(arg)?;
24007        }
24008        self.write(")");
24009        Ok(())
24010    }
24011
24012    fn generate_apply(&mut self, e: &Apply) -> Result<()> {
24013        // Python: return f"{this} APPLY({expr})"
24014        self.generate_expression(&e.this)?;
24015        self.write_space();
24016        self.write_keyword("APPLY");
24017        self.write("(");
24018        self.generate_expression(&e.expression)?;
24019        self.write(")");
24020        Ok(())
24021    }
24022
24023    fn generate_approx_percentile_estimate(&mut self, e: &ApproxPercentileEstimate) -> Result<()> {
24024        // APPROX_PERCENTILE_ESTIMATE(this, percentile)
24025        self.write_keyword("APPROX_PERCENTILE_ESTIMATE");
24026        self.write("(");
24027        self.generate_expression(&e.this)?;
24028        if let Some(percentile) = &e.percentile {
24029            self.write(", ");
24030            self.generate_expression(percentile)?;
24031        }
24032        self.write(")");
24033        Ok(())
24034    }
24035
24036    fn generate_approx_quantile(&mut self, e: &ApproxQuantile) -> Result<()> {
24037        // APPROX_QUANTILE(this, quantile[, accuracy][, weight])
24038        self.write_keyword("APPROX_QUANTILE");
24039        self.write("(");
24040        self.generate_expression(&e.this)?;
24041        if let Some(quantile) = &e.quantile {
24042            self.write(", ");
24043            self.generate_expression(quantile)?;
24044        }
24045        if let Some(accuracy) = &e.accuracy {
24046            self.write(", ");
24047            self.generate_expression(accuracy)?;
24048        }
24049        if let Some(weight) = &e.weight {
24050            self.write(", ");
24051            self.generate_expression(weight)?;
24052        }
24053        self.write(")");
24054        Ok(())
24055    }
24056
24057    fn generate_approx_quantiles(&mut self, e: &ApproxQuantiles) -> Result<()> {
24058        // APPROX_QUANTILES(this, expression)
24059        self.write_keyword("APPROX_QUANTILES");
24060        self.write("(");
24061        self.generate_expression(&e.this)?;
24062        if let Some(expression) = &e.expression {
24063            self.write(", ");
24064            self.generate_expression(expression)?;
24065        }
24066        self.write(")");
24067        Ok(())
24068    }
24069
24070    fn generate_approx_top_k(&mut self, e: &ApproxTopK) -> Result<()> {
24071        // APPROX_TOP_K(this[, expression][, counters])
24072        self.write_keyword("APPROX_TOP_K");
24073        self.write("(");
24074        self.generate_expression(&e.this)?;
24075        if let Some(expression) = &e.expression {
24076            self.write(", ");
24077            self.generate_expression(expression)?;
24078        }
24079        if let Some(counters) = &e.counters {
24080            self.write(", ");
24081            self.generate_expression(counters)?;
24082        }
24083        self.write(")");
24084        Ok(())
24085    }
24086
24087    fn generate_approx_top_k_accumulate(&mut self, e: &ApproxTopKAccumulate) -> Result<()> {
24088        // APPROX_TOP_K_ACCUMULATE(this[, expression])
24089        self.write_keyword("APPROX_TOP_K_ACCUMULATE");
24090        self.write("(");
24091        self.generate_expression(&e.this)?;
24092        if let Some(expression) = &e.expression {
24093            self.write(", ");
24094            self.generate_expression(expression)?;
24095        }
24096        self.write(")");
24097        Ok(())
24098    }
24099
24100    fn generate_approx_top_k_combine(&mut self, e: &ApproxTopKCombine) -> Result<()> {
24101        // APPROX_TOP_K_COMBINE(this[, expression])
24102        self.write_keyword("APPROX_TOP_K_COMBINE");
24103        self.write("(");
24104        self.generate_expression(&e.this)?;
24105        if let Some(expression) = &e.expression {
24106            self.write(", ");
24107            self.generate_expression(expression)?;
24108        }
24109        self.write(")");
24110        Ok(())
24111    }
24112
24113    fn generate_approx_top_k_estimate(&mut self, e: &ApproxTopKEstimate) -> Result<()> {
24114        // APPROX_TOP_K_ESTIMATE(this[, expression])
24115        self.write_keyword("APPROX_TOP_K_ESTIMATE");
24116        self.write("(");
24117        self.generate_expression(&e.this)?;
24118        if let Some(expression) = &e.expression {
24119            self.write(", ");
24120            self.generate_expression(expression)?;
24121        }
24122        self.write(")");
24123        Ok(())
24124    }
24125
24126    fn generate_approx_top_sum(&mut self, e: &ApproxTopSum) -> Result<()> {
24127        // APPROX_TOP_SUM(this, expression[, count])
24128        self.write_keyword("APPROX_TOP_SUM");
24129        self.write("(");
24130        self.generate_expression(&e.this)?;
24131        self.write(", ");
24132        self.generate_expression(&e.expression)?;
24133        if let Some(count) = &e.count {
24134            self.write(", ");
24135            self.generate_expression(count)?;
24136        }
24137        self.write(")");
24138        Ok(())
24139    }
24140
24141    fn generate_arg_max(&mut self, e: &ArgMax) -> Result<()> {
24142        // ARG_MAX(this, expression[, count])
24143        self.write_keyword("ARG_MAX");
24144        self.write("(");
24145        self.generate_expression(&e.this)?;
24146        self.write(", ");
24147        self.generate_expression(&e.expression)?;
24148        if let Some(count) = &e.count {
24149            self.write(", ");
24150            self.generate_expression(count)?;
24151        }
24152        self.write(")");
24153        Ok(())
24154    }
24155
24156    fn generate_arg_min(&mut self, e: &ArgMin) -> Result<()> {
24157        // ARG_MIN(this, expression[, count])
24158        self.write_keyword("ARG_MIN");
24159        self.write("(");
24160        self.generate_expression(&e.this)?;
24161        self.write(", ");
24162        self.generate_expression(&e.expression)?;
24163        if let Some(count) = &e.count {
24164            self.write(", ");
24165            self.generate_expression(count)?;
24166        }
24167        self.write(")");
24168        Ok(())
24169    }
24170
24171    fn generate_array_all(&mut self, e: &ArrayAll) -> Result<()> {
24172        // ARRAY_ALL(this, expression)
24173        self.write_keyword("ARRAY_ALL");
24174        self.write("(");
24175        self.generate_expression(&e.this)?;
24176        self.write(", ");
24177        self.generate_expression(&e.expression)?;
24178        self.write(")");
24179        Ok(())
24180    }
24181
24182    fn generate_array_any(&mut self, e: &ArrayAny) -> Result<()> {
24183        // ARRAY_ANY(this, expression) - fallback implementation
24184        self.write_keyword("ARRAY_ANY");
24185        self.write("(");
24186        self.generate_expression(&e.this)?;
24187        self.write(", ");
24188        self.generate_expression(&e.expression)?;
24189        self.write(")");
24190        Ok(())
24191    }
24192
24193    fn generate_array_construct_compact(&mut self, e: &ArrayConstructCompact) -> Result<()> {
24194        // ARRAY_CONSTRUCT_COMPACT(expressions...)
24195        self.write_keyword("ARRAY_CONSTRUCT_COMPACT");
24196        self.write("(");
24197        for (i, expr) in e.expressions.iter().enumerate() {
24198            if i > 0 {
24199                self.write(", ");
24200            }
24201            self.generate_expression(expr)?;
24202        }
24203        self.write(")");
24204        Ok(())
24205    }
24206
24207    fn generate_array_sum(&mut self, e: &ArraySum) -> Result<()> {
24208        // ARRAY_SUM(this[, expression])
24209        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
24210            self.write("arraySum");
24211        } else {
24212            self.write_keyword("ARRAY_SUM");
24213        }
24214        self.write("(");
24215        self.generate_expression(&e.this)?;
24216        if let Some(expression) = &e.expression {
24217            self.write(", ");
24218            self.generate_expression(expression)?;
24219        }
24220        self.write(")");
24221        Ok(())
24222    }
24223
24224    fn generate_at_index(&mut self, e: &AtIndex) -> Result<()> {
24225        // Python: return f"{this} AT {index}"
24226        self.generate_expression(&e.this)?;
24227        self.write_space();
24228        self.write_keyword("AT");
24229        self.write_space();
24230        self.generate_expression(&e.expression)?;
24231        Ok(())
24232    }
24233
24234    fn generate_attach(&mut self, e: &Attach) -> Result<()> {
24235        // Python: return f"ATTACH{exists_sql} {this}{expressions}"
24236        self.write_keyword("ATTACH");
24237        if e.exists {
24238            self.write_space();
24239            self.write_keyword("IF NOT EXISTS");
24240        }
24241        self.write_space();
24242        self.generate_expression(&e.this)?;
24243        if !e.expressions.is_empty() {
24244            self.write(" (");
24245            for (i, expr) in e.expressions.iter().enumerate() {
24246                if i > 0 {
24247                    self.write(", ");
24248                }
24249                self.generate_expression(expr)?;
24250            }
24251            self.write(")");
24252        }
24253        Ok(())
24254    }
24255
24256    fn generate_attach_option(&mut self, e: &AttachOption) -> Result<()> {
24257        // AttachOption: this [expression]
24258        // Python sqlglot: no equals sign, just space-separated
24259        self.generate_expression(&e.this)?;
24260        if let Some(expression) = &e.expression {
24261            self.write_space();
24262            self.generate_expression(expression)?;
24263        }
24264        Ok(())
24265    }
24266
24267    /// Generate the auto_increment keyword and options for a column definition.
24268    /// Different dialects use different syntax: IDENTITY, AUTOINCREMENT, AUTO_INCREMENT,
24269    /// GENERATED AS IDENTITY, etc.
24270    fn generate_auto_increment_keyword(
24271        &mut self,
24272        col: &crate::expressions::ColumnDef,
24273    ) -> Result<()> {
24274        use crate::dialects::DialectType;
24275        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
24276            self.write_keyword("IDENTITY");
24277            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
24278                self.write("(");
24279                if let Some(ref start) = col.auto_increment_start {
24280                    self.generate_expression(start)?;
24281                } else {
24282                    self.write("0");
24283                }
24284                self.write(", ");
24285                if let Some(ref inc) = col.auto_increment_increment {
24286                    self.generate_expression(inc)?;
24287                } else {
24288                    self.write("1");
24289                }
24290                self.write(")");
24291            }
24292        } else if matches!(
24293            self.config.dialect,
24294            Some(DialectType::Snowflake) | Some(DialectType::SQLite)
24295        ) {
24296            self.write_keyword("AUTOINCREMENT");
24297            if let Some(ref start) = col.auto_increment_start {
24298                self.write_space();
24299                self.write_keyword("START");
24300                self.write_space();
24301                self.generate_expression(start)?;
24302            }
24303            if let Some(ref inc) = col.auto_increment_increment {
24304                self.write_space();
24305                self.write_keyword("INCREMENT");
24306                self.write_space();
24307                self.generate_expression(inc)?;
24308            }
24309            if let Some(order) = col.auto_increment_order {
24310                self.write_space();
24311                if order {
24312                    self.write_keyword("ORDER");
24313                } else {
24314                    self.write_keyword("NOORDER");
24315                }
24316            }
24317        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
24318            self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
24319            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
24320                self.write(" (");
24321                let mut first = true;
24322                if let Some(ref start) = col.auto_increment_start {
24323                    self.write_keyword("START WITH");
24324                    self.write_space();
24325                    self.generate_expression(start)?;
24326                    first = false;
24327                }
24328                if let Some(ref inc) = col.auto_increment_increment {
24329                    if !first {
24330                        self.write_space();
24331                    }
24332                    self.write_keyword("INCREMENT BY");
24333                    self.write_space();
24334                    self.generate_expression(inc)?;
24335                }
24336                self.write(")");
24337            }
24338        } else if matches!(self.config.dialect, Some(DialectType::Databricks)) {
24339            self.write_keyword("GENERATED ALWAYS AS IDENTITY");
24340            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
24341                self.write(" (");
24342                let mut first = true;
24343                if let Some(ref start) = col.auto_increment_start {
24344                    self.write_keyword("START WITH");
24345                    self.write_space();
24346                    self.generate_expression(start)?;
24347                    first = false;
24348                }
24349                if let Some(ref inc) = col.auto_increment_increment {
24350                    if !first {
24351                        self.write_space();
24352                    }
24353                    self.write_keyword("INCREMENT BY");
24354                    self.write_space();
24355                    self.generate_expression(inc)?;
24356                }
24357                self.write(")");
24358            }
24359        } else if matches!(
24360            self.config.dialect,
24361            Some(DialectType::TSQL) | Some(DialectType::Fabric)
24362        ) {
24363            self.write_keyword("IDENTITY");
24364            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
24365                self.write("(");
24366                if let Some(ref start) = col.auto_increment_start {
24367                    self.generate_expression(start)?;
24368                } else {
24369                    self.write("0");
24370                }
24371                self.write(", ");
24372                if let Some(ref inc) = col.auto_increment_increment {
24373                    self.generate_expression(inc)?;
24374                } else {
24375                    self.write("1");
24376                }
24377                self.write(")");
24378            }
24379        } else {
24380            self.write_keyword("AUTO_INCREMENT");
24381            if let Some(ref start) = col.auto_increment_start {
24382                self.write_space();
24383                self.write_keyword("START");
24384                self.write_space();
24385                self.generate_expression(start)?;
24386            }
24387            if let Some(ref inc) = col.auto_increment_increment {
24388                self.write_space();
24389                self.write_keyword("INCREMENT");
24390                self.write_space();
24391                self.generate_expression(inc)?;
24392            }
24393            if let Some(order) = col.auto_increment_order {
24394                self.write_space();
24395                if order {
24396                    self.write_keyword("ORDER");
24397                } else {
24398                    self.write_keyword("NOORDER");
24399                }
24400            }
24401        }
24402        Ok(())
24403    }
24404
24405    fn generate_auto_increment_property(&mut self, e: &AutoIncrementProperty) -> Result<()> {
24406        // AUTO_INCREMENT=value
24407        self.write_keyword("AUTO_INCREMENT");
24408        self.write("=");
24409        self.generate_expression(&e.this)?;
24410        Ok(())
24411    }
24412
24413    fn generate_auto_refresh_property(&mut self, e: &AutoRefreshProperty) -> Result<()> {
24414        // AUTO_REFRESH=value
24415        self.write_keyword("AUTO_REFRESH");
24416        self.write("=");
24417        self.generate_expression(&e.this)?;
24418        Ok(())
24419    }
24420
24421    fn generate_backup_property(&mut self, e: &BackupProperty) -> Result<()> {
24422        // BACKUP YES|NO (Redshift syntax uses space, not equals)
24423        self.write_keyword("BACKUP");
24424        self.write_space();
24425        self.generate_expression(&e.this)?;
24426        Ok(())
24427    }
24428
24429    fn generate_base64_decode_binary(&mut self, e: &Base64DecodeBinary) -> Result<()> {
24430        // BASE64_DECODE_BINARY(this[, alphabet])
24431        self.write_keyword("BASE64_DECODE_BINARY");
24432        self.write("(");
24433        self.generate_expression(&e.this)?;
24434        if let Some(alphabet) = &e.alphabet {
24435            self.write(", ");
24436            self.generate_expression(alphabet)?;
24437        }
24438        self.write(")");
24439        Ok(())
24440    }
24441
24442    fn generate_base64_decode_string(&mut self, e: &Base64DecodeString) -> Result<()> {
24443        // BASE64_DECODE_STRING(this[, alphabet])
24444        self.write_keyword("BASE64_DECODE_STRING");
24445        self.write("(");
24446        self.generate_expression(&e.this)?;
24447        if let Some(alphabet) = &e.alphabet {
24448            self.write(", ");
24449            self.generate_expression(alphabet)?;
24450        }
24451        self.write(")");
24452        Ok(())
24453    }
24454
24455    fn generate_base64_encode(&mut self, e: &Base64Encode) -> Result<()> {
24456        // BASE64_ENCODE(this[, max_line_length][, alphabet])
24457        self.write_keyword("BASE64_ENCODE");
24458        self.write("(");
24459        self.generate_expression(&e.this)?;
24460        if let Some(max_line_length) = &e.max_line_length {
24461            self.write(", ");
24462            self.generate_expression(max_line_length)?;
24463        }
24464        if let Some(alphabet) = &e.alphabet {
24465            self.write(", ");
24466            self.generate_expression(alphabet)?;
24467        }
24468        self.write(")");
24469        Ok(())
24470    }
24471
24472    fn generate_block_compression_property(&mut self, e: &BlockCompressionProperty) -> Result<()> {
24473        // BLOCKCOMPRESSION=... (complex Teradata property)
24474        self.write_keyword("BLOCKCOMPRESSION");
24475        self.write("=");
24476        if let Some(autotemp) = &e.autotemp {
24477            self.write_keyword("AUTOTEMP");
24478            self.write("(");
24479            self.generate_expression(autotemp)?;
24480            self.write(")");
24481        }
24482        if let Some(always) = &e.always {
24483            self.generate_expression(always)?;
24484        }
24485        if let Some(default) = &e.default {
24486            self.generate_expression(default)?;
24487        }
24488        if let Some(manual) = &e.manual {
24489            self.generate_expression(manual)?;
24490        }
24491        if let Some(never) = &e.never {
24492            self.generate_expression(never)?;
24493        }
24494        Ok(())
24495    }
24496
24497    fn generate_booland(&mut self, e: &Booland) -> Result<()> {
24498        // Python: return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
24499        self.write("((");
24500        self.generate_expression(&e.this)?;
24501        self.write(") ");
24502        self.write_keyword("AND");
24503        self.write(" (");
24504        self.generate_expression(&e.expression)?;
24505        self.write("))");
24506        Ok(())
24507    }
24508
24509    fn generate_boolor(&mut self, e: &Boolor) -> Result<()> {
24510        // Python: return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
24511        self.write("((");
24512        self.generate_expression(&e.this)?;
24513        self.write(") ");
24514        self.write_keyword("OR");
24515        self.write(" (");
24516        self.generate_expression(&e.expression)?;
24517        self.write("))");
24518        Ok(())
24519    }
24520
24521    fn generate_build_property(&mut self, e: &BuildProperty) -> Result<()> {
24522        // BUILD value (e.g., BUILD IMMEDIATE, BUILD DEFERRED)
24523        self.write_keyword("BUILD");
24524        self.write_space();
24525        self.generate_expression(&e.this)?;
24526        Ok(())
24527    }
24528
24529    fn generate_byte_string(&mut self, e: &ByteString) -> Result<()> {
24530        // Byte string literal like B'...' or X'...'
24531        self.generate_expression(&e.this)?;
24532        Ok(())
24533    }
24534
24535    fn generate_case_specific_column_constraint(
24536        &mut self,
24537        e: &CaseSpecificColumnConstraint,
24538    ) -> Result<()> {
24539        // CASESPECIFIC or NOT CASESPECIFIC (Teradata)
24540        if e.not_.is_some() {
24541            self.write_keyword("NOT");
24542            self.write_space();
24543        }
24544        self.write_keyword("CASESPECIFIC");
24545        Ok(())
24546    }
24547
24548    fn generate_cast_to_str_type(&mut self, e: &CastToStrType) -> Result<()> {
24549        // Cast to string type (dialect-specific)
24550        self.write_keyword("CAST");
24551        self.write("(");
24552        self.generate_expression(&e.this)?;
24553        if self.config.dialect == Some(DialectType::ClickHouse) {
24554            // ClickHouse: CAST(expr, 'type_string')
24555            self.write(", ");
24556        } else {
24557            self.write_space();
24558            self.write_keyword("AS");
24559            self.write_space();
24560        }
24561        if let Some(to) = &e.to {
24562            self.generate_expression(to)?;
24563        }
24564        self.write(")");
24565        Ok(())
24566    }
24567
24568    fn generate_changes(&mut self, e: &Changes) -> Result<()> {
24569        // CHANGES (INFORMATION => value) AT|BEFORE (...) END (...)
24570        // Python: f"CHANGES ({information}){at_before}{end}"
24571        self.write_keyword("CHANGES");
24572        self.write(" (");
24573        if let Some(information) = &e.information {
24574            self.write_keyword("INFORMATION");
24575            self.write(" => ");
24576            self.generate_expression(information)?;
24577        }
24578        self.write(")");
24579        // at_before and end are HistoricalData expressions that generate their own keywords
24580        if let Some(at_before) = &e.at_before {
24581            self.write(" ");
24582            self.generate_expression(at_before)?;
24583        }
24584        if let Some(end) = &e.end {
24585            self.write(" ");
24586            self.generate_expression(end)?;
24587        }
24588        Ok(())
24589    }
24590
24591    fn generate_character_set_column_constraint(
24592        &mut self,
24593        e: &CharacterSetColumnConstraint,
24594    ) -> Result<()> {
24595        // CHARACTER SET charset_name
24596        self.write_keyword("CHARACTER SET");
24597        self.write_space();
24598        self.generate_expression(&e.this)?;
24599        Ok(())
24600    }
24601
24602    fn generate_character_set_property(&mut self, e: &CharacterSetProperty) -> Result<()> {
24603        // [DEFAULT] CHARACTER SET=value
24604        if e.default.is_some() {
24605            self.write_keyword("DEFAULT");
24606            self.write_space();
24607        }
24608        self.write_keyword("CHARACTER SET");
24609        self.write("=");
24610        self.generate_expression(&e.this)?;
24611        Ok(())
24612    }
24613
24614    fn generate_check_column_constraint(&mut self, e: &CheckColumnConstraint) -> Result<()> {
24615        // Python: return f"CHECK ({self.sql(expression, 'this')}){enforced}"
24616        self.write_keyword("CHECK");
24617        self.write(" (");
24618        self.generate_expression(&e.this)?;
24619        self.write(")");
24620        if e.enforced.is_some() {
24621            self.write_space();
24622            self.write_keyword("ENFORCED");
24623        }
24624        Ok(())
24625    }
24626
24627    fn generate_check_json(&mut self, e: &CheckJson) -> Result<()> {
24628        // CHECK_JSON(this)
24629        self.write_keyword("CHECK_JSON");
24630        self.write("(");
24631        self.generate_expression(&e.this)?;
24632        self.write(")");
24633        Ok(())
24634    }
24635
24636    fn generate_check_xml(&mut self, e: &CheckXml) -> Result<()> {
24637        // CHECK_XML(this)
24638        self.write_keyword("CHECK_XML");
24639        self.write("(");
24640        self.generate_expression(&e.this)?;
24641        self.write(")");
24642        Ok(())
24643    }
24644
24645    fn generate_checksum_property(&mut self, e: &ChecksumProperty) -> Result<()> {
24646        // CHECKSUM=[ON|OFF|DEFAULT]
24647        self.write_keyword("CHECKSUM");
24648        self.write("=");
24649        if e.on.is_some() {
24650            self.write_keyword("ON");
24651        } else if e.default.is_some() {
24652            self.write_keyword("DEFAULT");
24653        } else {
24654            self.write_keyword("OFF");
24655        }
24656        Ok(())
24657    }
24658
24659    fn generate_clone(&mut self, e: &Clone) -> Result<()> {
24660        // Python: return f"{shallow}{keyword} {this}"
24661        if e.shallow.is_some() {
24662            self.write_keyword("SHALLOW");
24663            self.write_space();
24664        }
24665        if e.copy.is_some() {
24666            self.write_keyword("COPY");
24667        } else {
24668            self.write_keyword("CLONE");
24669        }
24670        self.write_space();
24671        self.generate_expression(&e.this)?;
24672        Ok(())
24673    }
24674
24675    fn generate_cluster_by(&mut self, e: &ClusterBy) -> Result<()> {
24676        // CLUSTER BY (expressions)
24677        self.write_keyword("CLUSTER BY");
24678        self.write(" (");
24679        for (i, ord) in e.expressions.iter().enumerate() {
24680            if i > 0 {
24681                self.write(", ");
24682            }
24683            self.generate_ordered(ord)?;
24684        }
24685        self.write(")");
24686        Ok(())
24687    }
24688
24689    fn generate_clustered_by_property(&mut self, e: &ClusteredByProperty) -> Result<()> {
24690        // Python: return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
24691        self.write_keyword("CLUSTERED BY");
24692        self.write(" (");
24693        for (i, expr) in e.expressions.iter().enumerate() {
24694            if i > 0 {
24695                self.write(", ");
24696            }
24697            self.generate_expression(expr)?;
24698        }
24699        self.write(")");
24700        if let Some(sorted_by) = &e.sorted_by {
24701            self.write_space();
24702            self.write_keyword("SORTED BY");
24703            self.write(" (");
24704            // Unwrap Tuple to avoid double parentheses
24705            if let Expression::Tuple(t) = sorted_by.as_ref() {
24706                for (i, expr) in t.expressions.iter().enumerate() {
24707                    if i > 0 {
24708                        self.write(", ");
24709                    }
24710                    self.generate_expression(expr)?;
24711                }
24712            } else {
24713                self.generate_expression(sorted_by)?;
24714            }
24715            self.write(")");
24716        }
24717        if let Some(buckets) = &e.buckets {
24718            self.write_space();
24719            self.write_keyword("INTO");
24720            self.write_space();
24721            self.generate_expression(buckets)?;
24722            self.write_space();
24723            self.write_keyword("BUCKETS");
24724        }
24725        Ok(())
24726    }
24727
24728    fn generate_collate_property(&mut self, e: &CollateProperty) -> Result<()> {
24729        // [DEFAULT] COLLATE [=] value
24730        // BigQuery uses space: DEFAULT COLLATE 'en'
24731        // Others use equals: COLLATE='en'
24732        if e.default.is_some() {
24733            self.write_keyword("DEFAULT");
24734            self.write_space();
24735        }
24736        self.write_keyword("COLLATE");
24737        // BigQuery uses space between COLLATE and value
24738        match self.config.dialect {
24739            Some(DialectType::BigQuery) => self.write_space(),
24740            _ => self.write("="),
24741        }
24742        self.generate_expression(&e.this)?;
24743        Ok(())
24744    }
24745
24746    fn generate_column_constraint(&mut self, e: &ColumnConstraint) -> Result<()> {
24747        // ColumnConstraint is an enum
24748        match e {
24749            ColumnConstraint::NotNull => {
24750                self.write_keyword("NOT NULL");
24751            }
24752            ColumnConstraint::Null => {
24753                self.write_keyword("NULL");
24754            }
24755            ColumnConstraint::Unique => {
24756                self.write_keyword("UNIQUE");
24757            }
24758            ColumnConstraint::PrimaryKey => {
24759                self.write_keyword("PRIMARY KEY");
24760            }
24761            ColumnConstraint::Default(expr) => {
24762                self.write_keyword("DEFAULT");
24763                self.write_space();
24764                self.generate_expression(expr)?;
24765            }
24766            ColumnConstraint::Check(expr) => {
24767                self.write_keyword("CHECK");
24768                self.write(" (");
24769                self.generate_expression(expr)?;
24770                self.write(")");
24771            }
24772            ColumnConstraint::References(fk_ref) => {
24773                if fk_ref.has_foreign_key_keywords {
24774                    self.write_keyword("FOREIGN KEY");
24775                    self.write_space();
24776                }
24777                self.write_keyword("REFERENCES");
24778                self.write_space();
24779                self.generate_table(&fk_ref.table)?;
24780                if !fk_ref.columns.is_empty() {
24781                    self.write(" (");
24782                    for (i, col) in fk_ref.columns.iter().enumerate() {
24783                        if i > 0 {
24784                            self.write(", ");
24785                        }
24786                        self.generate_identifier(col)?;
24787                    }
24788                    self.write(")");
24789                }
24790            }
24791            ColumnConstraint::GeneratedAsIdentity(gen) => {
24792                self.write_keyword("GENERATED");
24793                self.write_space();
24794                if gen.always {
24795                    self.write_keyword("ALWAYS");
24796                } else {
24797                    self.write_keyword("BY DEFAULT");
24798                    if gen.on_null {
24799                        self.write_space();
24800                        self.write_keyword("ON NULL");
24801                    }
24802                }
24803                self.write_space();
24804                self.write_keyword("AS IDENTITY");
24805            }
24806            ColumnConstraint::Collate(collation) => {
24807                self.write_keyword("COLLATE");
24808                self.write_space();
24809                self.generate_identifier(collation)?;
24810            }
24811            ColumnConstraint::Comment(comment) => {
24812                self.write_keyword("COMMENT");
24813                self.write(" '");
24814                self.write(comment);
24815                self.write("'");
24816            }
24817            ColumnConstraint::ComputedColumn(cc) => {
24818                self.generate_computed_column_inline(cc)?;
24819            }
24820            ColumnConstraint::GeneratedAsRow(gar) => {
24821                self.generate_generated_as_row_inline(gar)?;
24822            }
24823            ColumnConstraint::Tags(tags) => {
24824                self.write_keyword("TAG");
24825                self.write(" (");
24826                for (i, expr) in tags.expressions.iter().enumerate() {
24827                    if i > 0 {
24828                        self.write(", ");
24829                    }
24830                    self.generate_expression(expr)?;
24831                }
24832                self.write(")");
24833            }
24834            ColumnConstraint::Path(path_expr) => {
24835                self.write_keyword("PATH");
24836                self.write_space();
24837                self.generate_expression(path_expr)?;
24838            }
24839        }
24840        Ok(())
24841    }
24842
24843    fn generate_column_position(&mut self, e: &ColumnPosition) -> Result<()> {
24844        // ColumnPosition is an enum
24845        match e {
24846            ColumnPosition::First => {
24847                self.write_keyword("FIRST");
24848            }
24849            ColumnPosition::After(ident) => {
24850                self.write_keyword("AFTER");
24851                self.write_space();
24852                self.generate_identifier(ident)?;
24853            }
24854        }
24855        Ok(())
24856    }
24857
24858    fn generate_column_prefix(&mut self, e: &ColumnPrefix) -> Result<()> {
24859        // column(prefix)
24860        self.generate_expression(&e.this)?;
24861        self.write("(");
24862        self.generate_expression(&e.expression)?;
24863        self.write(")");
24864        Ok(())
24865    }
24866
24867    fn generate_columns(&mut self, e: &Columns) -> Result<()> {
24868        // If unpack is true, this came from * COLUMNS(pattern)
24869        // DuckDB syntax: * COLUMNS(c ILIKE '%suffix') or COLUMNS(pattern)
24870        if let Some(ref unpack) = e.unpack {
24871            if let Expression::Boolean(b) = unpack.as_ref() {
24872                if b.value {
24873                    self.write("*");
24874                }
24875            }
24876        }
24877        self.write_keyword("COLUMNS");
24878        self.write("(");
24879        self.generate_expression(&e.this)?;
24880        self.write(")");
24881        Ok(())
24882    }
24883
24884    fn generate_combined_agg_func(&mut self, e: &CombinedAggFunc) -> Result<()> {
24885        // Combined aggregate: FUNC(args) combined
24886        self.generate_expression(&e.this)?;
24887        self.write("(");
24888        for (i, expr) in e.expressions.iter().enumerate() {
24889            if i > 0 {
24890                self.write(", ");
24891            }
24892            self.generate_expression(expr)?;
24893        }
24894        self.write(")");
24895        Ok(())
24896    }
24897
24898    fn generate_combined_parameterized_agg(&mut self, e: &CombinedParameterizedAgg) -> Result<()> {
24899        // Combined parameterized aggregate: FUNC(params)(expressions)
24900        self.generate_expression(&e.this)?;
24901        self.write("(");
24902        for (i, param) in e.params.iter().enumerate() {
24903            if i > 0 {
24904                self.write(", ");
24905            }
24906            self.generate_expression(param)?;
24907        }
24908        self.write(")(");
24909        for (i, expr) in e.expressions.iter().enumerate() {
24910            if i > 0 {
24911                self.write(", ");
24912            }
24913            self.generate_expression(expr)?;
24914        }
24915        self.write(")");
24916        Ok(())
24917    }
24918
24919    fn generate_commit(&mut self, e: &Commit) -> Result<()> {
24920        // COMMIT [TRANSACTION [transaction_name]] [WITH (DELAYED_DURABILITY = ON|OFF)] [AND [NO] CHAIN]
24921        self.write_keyword("COMMIT");
24922
24923        // TSQL always uses COMMIT TRANSACTION
24924        if e.this.is_none()
24925            && matches!(
24926                self.config.dialect,
24927                Some(DialectType::TSQL) | Some(DialectType::Fabric)
24928            )
24929        {
24930            self.write_space();
24931            self.write_keyword("TRANSACTION");
24932        }
24933
24934        // Check if this has TRANSACTION keyword or transaction name
24935        if let Some(this) = &e.this {
24936            // Check if it's just the "TRANSACTION" marker or an actual transaction name
24937            let is_transaction_marker = matches!(
24938                this.as_ref(),
24939                Expression::Identifier(id) if id.name == "TRANSACTION"
24940            );
24941
24942            self.write_space();
24943            self.write_keyword("TRANSACTION");
24944
24945            // If it's a real transaction name, output it
24946            if !is_transaction_marker {
24947                self.write_space();
24948                self.generate_expression(this)?;
24949            }
24950        }
24951
24952        // Output WITH (DELAYED_DURABILITY = ON|OFF) for TSQL
24953        if let Some(durability) = &e.durability {
24954            self.write_space();
24955            self.write_keyword("WITH");
24956            self.write(" (");
24957            self.write_keyword("DELAYED_DURABILITY");
24958            self.write(" = ");
24959            if let Expression::Boolean(BooleanLiteral { value: true }) = durability.as_ref() {
24960                self.write_keyword("ON");
24961            } else {
24962                self.write_keyword("OFF");
24963            }
24964            self.write(")");
24965        }
24966
24967        // Output AND [NO] CHAIN
24968        if let Some(chain) = &e.chain {
24969            self.write_space();
24970            if let Expression::Boolean(BooleanLiteral { value: false }) = chain.as_ref() {
24971                self.write_keyword("AND NO CHAIN");
24972            } else {
24973                self.write_keyword("AND CHAIN");
24974            }
24975        }
24976        Ok(())
24977    }
24978
24979    fn generate_comprehension(&mut self, e: &Comprehension) -> Result<()> {
24980        // Python-style comprehension: [expr FOR var[, pos] IN iterator IF condition]
24981        self.write("[");
24982        self.generate_expression(&e.this)?;
24983        self.write_space();
24984        self.write_keyword("FOR");
24985        self.write_space();
24986        self.generate_expression(&e.expression)?;
24987        // Handle optional position variable (for enumerate-like syntax)
24988        if let Some(pos) = &e.position {
24989            self.write(", ");
24990            self.generate_expression(pos)?;
24991        }
24992        if let Some(iterator) = &e.iterator {
24993            self.write_space();
24994            self.write_keyword("IN");
24995            self.write_space();
24996            self.generate_expression(iterator)?;
24997        }
24998        if let Some(condition) = &e.condition {
24999            self.write_space();
25000            self.write_keyword("IF");
25001            self.write_space();
25002            self.generate_expression(condition)?;
25003        }
25004        self.write("]");
25005        Ok(())
25006    }
25007
25008    fn generate_compress(&mut self, e: &Compress) -> Result<()> {
25009        // COMPRESS(this[, method])
25010        self.write_keyword("COMPRESS");
25011        self.write("(");
25012        self.generate_expression(&e.this)?;
25013        if let Some(method) = &e.method {
25014            self.write(", '");
25015            self.write(method);
25016            self.write("'");
25017        }
25018        self.write(")");
25019        Ok(())
25020    }
25021
25022    fn generate_compress_column_constraint(&mut self, e: &CompressColumnConstraint) -> Result<()> {
25023        // Python: return f"COMPRESS {this}"
25024        self.write_keyword("COMPRESS");
25025        if let Some(this) = &e.this {
25026            self.write_space();
25027            self.generate_expression(this)?;
25028        }
25029        Ok(())
25030    }
25031
25032    fn generate_computed_column_constraint(&mut self, e: &ComputedColumnConstraint) -> Result<()> {
25033        // Python: return f"AS {this}{persisted}"
25034        self.write_keyword("AS");
25035        self.write_space();
25036        self.generate_expression(&e.this)?;
25037        if e.not_null.is_some() {
25038            self.write_space();
25039            self.write_keyword("PERSISTED NOT NULL");
25040        } else if e.persisted.is_some() {
25041            self.write_space();
25042            self.write_keyword("PERSISTED");
25043        }
25044        Ok(())
25045    }
25046
25047    /// Generate a ComputedColumn constraint inline within a column definition.
25048    /// Handles MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
25049    /// Handles TSQL: AS (expr) [PERSISTED] [NOT NULL]
25050    fn generate_computed_column_inline(&mut self, cc: &ComputedColumn) -> Result<()> {
25051        let computed_expr = if matches!(
25052            self.config.dialect,
25053            Some(DialectType::TSQL) | Some(DialectType::Fabric)
25054        ) {
25055            match &*cc.expression {
25056                Expression::Year(y) if !matches!(&y.this, Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
25057                {
25058                    let wrapped = Expression::Cast(Box::new(Cast {
25059                        this: y.this.clone(),
25060                        to: DataType::Date,
25061                        trailing_comments: Vec::new(),
25062                        double_colon_syntax: false,
25063                        format: None,
25064                        default: None,
25065                    }));
25066                    Expression::Year(Box::new(UnaryFunc::new(wrapped)))
25067                }
25068                Expression::Function(f)
25069                    if f.name.eq_ignore_ascii_case("YEAR")
25070                        && f.args.len() == 1
25071                        && !matches!(&f.args[0], Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
25072                {
25073                    let wrapped = Expression::Cast(Box::new(Cast {
25074                        this: f.args[0].clone(),
25075                        to: DataType::Date,
25076                        trailing_comments: Vec::new(),
25077                        double_colon_syntax: false,
25078                        format: None,
25079                        default: None,
25080                    }));
25081                    Expression::Function(Box::new(Function::new("YEAR".to_string(), vec![wrapped])))
25082                }
25083                _ => *cc.expression.clone(),
25084            }
25085        } else {
25086            *cc.expression.clone()
25087        };
25088
25089        match cc.persistence_kind.as_deref() {
25090            Some("STORED") | Some("VIRTUAL") => {
25091                // MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
25092                self.write_keyword("GENERATED ALWAYS AS");
25093                self.write(" (");
25094                self.generate_expression(&computed_expr)?;
25095                self.write(")");
25096                self.write_space();
25097                if cc.persisted {
25098                    self.write_keyword("STORED");
25099                } else {
25100                    self.write_keyword("VIRTUAL");
25101                }
25102            }
25103            Some("PERSISTED") => {
25104                // TSQL/SingleStore: AS (expr) PERSISTED [TYPE] [NOT NULL]
25105                self.write_keyword("AS");
25106                self.write(" (");
25107                self.generate_expression(&computed_expr)?;
25108                self.write(")");
25109                self.write_space();
25110                self.write_keyword("PERSISTED");
25111                // Output data type if present (SingleStore: PERSISTED TYPE NOT NULL)
25112                if let Some(ref dt) = cc.data_type {
25113                    self.write_space();
25114                    self.generate_data_type(dt)?;
25115                }
25116                if cc.not_null {
25117                    self.write_space();
25118                    self.write_keyword("NOT NULL");
25119                }
25120            }
25121            _ => {
25122                // Spark/Databricks/Hive: GENERATED ALWAYS AS (expr)
25123                // TSQL computed column without PERSISTED: AS (expr)
25124                if matches!(
25125                    self.config.dialect,
25126                    Some(DialectType::Spark)
25127                        | Some(DialectType::Databricks)
25128                        | Some(DialectType::Hive)
25129                ) {
25130                    self.write_keyword("GENERATED ALWAYS AS");
25131                    self.write(" (");
25132                    self.generate_expression(&computed_expr)?;
25133                    self.write(")");
25134                } else if matches!(
25135                    self.config.dialect,
25136                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
25137                ) {
25138                    self.write_keyword("AS");
25139                    let omit_parens = matches!(computed_expr, Expression::Year(_))
25140                        || matches!(&computed_expr, Expression::Function(f) if f.name.eq_ignore_ascii_case("YEAR"));
25141                    if omit_parens {
25142                        self.write_space();
25143                        self.generate_expression(&computed_expr)?;
25144                    } else {
25145                        self.write(" (");
25146                        self.generate_expression(&computed_expr)?;
25147                        self.write(")");
25148                    }
25149                } else {
25150                    self.write_keyword("AS");
25151                    self.write(" (");
25152                    self.generate_expression(&computed_expr)?;
25153                    self.write(")");
25154                }
25155            }
25156        }
25157        Ok(())
25158    }
25159
25160    /// Generate a GeneratedAsRow constraint inline within a column definition.
25161    /// TSQL temporal: GENERATED ALWAYS AS ROW START|END [HIDDEN]
25162    fn generate_generated_as_row_inline(&mut self, gar: &GeneratedAsRow) -> Result<()> {
25163        self.write_keyword("GENERATED ALWAYS AS ROW ");
25164        if gar.start {
25165            self.write_keyword("START");
25166        } else {
25167            self.write_keyword("END");
25168        }
25169        if gar.hidden {
25170            self.write_space();
25171            self.write_keyword("HIDDEN");
25172        }
25173        Ok(())
25174    }
25175
25176    /// Generate just the SYSTEM_VERSIONING=ON(...) content without WITH() wrapper.
25177    fn generate_system_versioning_content(
25178        &mut self,
25179        e: &WithSystemVersioningProperty,
25180    ) -> Result<()> {
25181        let mut parts = Vec::new();
25182
25183        if let Some(this) = &e.this {
25184            let mut s = String::from("HISTORY_TABLE=");
25185            let mut gen = Generator::new();
25186            gen.config = self.config.clone();
25187            gen.generate_expression(this)?;
25188            s.push_str(&gen.output);
25189            parts.push(s);
25190        }
25191
25192        if let Some(data_consistency) = &e.data_consistency {
25193            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
25194            let mut gen = Generator::new();
25195            gen.config = self.config.clone();
25196            gen.generate_expression(data_consistency)?;
25197            s.push_str(&gen.output);
25198            parts.push(s);
25199        }
25200
25201        if let Some(retention_period) = &e.retention_period {
25202            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
25203            let mut gen = Generator::new();
25204            gen.config = self.config.clone();
25205            gen.generate_expression(retention_period)?;
25206            s.push_str(&gen.output);
25207            parts.push(s);
25208        }
25209
25210        self.write_keyword("SYSTEM_VERSIONING");
25211        self.write("=");
25212
25213        if !parts.is_empty() {
25214            self.write_keyword("ON");
25215            self.write("(");
25216            self.write(&parts.join(", "));
25217            self.write(")");
25218        } else if e.on.is_some() {
25219            self.write_keyword("ON");
25220        } else {
25221            self.write_keyword("OFF");
25222        }
25223
25224        Ok(())
25225    }
25226
25227    fn generate_conditional_insert(&mut self, e: &ConditionalInsert) -> Result<()> {
25228        // Conditional INSERT for multi-table inserts
25229        // Output: [WHEN cond THEN | ELSE] INTO table [(cols)] [VALUES (...)]
25230        if e.else_.is_some() {
25231            self.write_keyword("ELSE");
25232            self.write_space();
25233        } else if let Some(expression) = &e.expression {
25234            self.write_keyword("WHEN");
25235            self.write_space();
25236            self.generate_expression(expression)?;
25237            self.write_space();
25238            self.write_keyword("THEN");
25239            self.write_space();
25240        }
25241
25242        // Handle Insert expression specially - output "INTO table (cols) VALUES (...)"
25243        // without the "INSERT " prefix
25244        if let Expression::Insert(insert) = e.this.as_ref() {
25245            self.write_keyword("INTO");
25246            self.write_space();
25247            self.generate_table(&insert.table)?;
25248
25249            // Optional column list
25250            if !insert.columns.is_empty() {
25251                self.write(" (");
25252                for (i, col) in insert.columns.iter().enumerate() {
25253                    if i > 0 {
25254                        self.write(", ");
25255                    }
25256                    self.generate_identifier(col)?;
25257                }
25258                self.write(")");
25259            }
25260
25261            // Optional VALUES clause
25262            if !insert.values.is_empty() {
25263                self.write_space();
25264                self.write_keyword("VALUES");
25265                for (row_idx, row) in insert.values.iter().enumerate() {
25266                    if row_idx > 0 {
25267                        self.write(", ");
25268                    }
25269                    self.write(" (");
25270                    for (i, val) in row.iter().enumerate() {
25271                        if i > 0 {
25272                            self.write(", ");
25273                        }
25274                        self.generate_expression(val)?;
25275                    }
25276                    self.write(")");
25277                }
25278            }
25279        } else {
25280            // Fallback for non-Insert expressions
25281            self.generate_expression(&e.this)?;
25282        }
25283        Ok(())
25284    }
25285
25286    fn generate_constraint(&mut self, e: &Constraint) -> Result<()> {
25287        // Python: return f"CONSTRAINT {this} {expressions}"
25288        self.write_keyword("CONSTRAINT");
25289        self.write_space();
25290        self.generate_expression(&e.this)?;
25291        if !e.expressions.is_empty() {
25292            self.write_space();
25293            for (i, expr) in e.expressions.iter().enumerate() {
25294                if i > 0 {
25295                    self.write_space();
25296                }
25297                self.generate_expression(expr)?;
25298            }
25299        }
25300        Ok(())
25301    }
25302
25303    fn generate_convert_timezone(&mut self, e: &ConvertTimezone) -> Result<()> {
25304        // CONVERT_TIMEZONE([source_tz,] target_tz, timestamp)
25305        self.write_keyword("CONVERT_TIMEZONE");
25306        self.write("(");
25307        let mut first = true;
25308        if let Some(source_tz) = &e.source_tz {
25309            self.generate_expression(source_tz)?;
25310            first = false;
25311        }
25312        if let Some(target_tz) = &e.target_tz {
25313            if !first {
25314                self.write(", ");
25315            }
25316            self.generate_expression(target_tz)?;
25317            first = false;
25318        }
25319        if let Some(timestamp) = &e.timestamp {
25320            if !first {
25321                self.write(", ");
25322            }
25323            self.generate_expression(timestamp)?;
25324        }
25325        self.write(")");
25326        Ok(())
25327    }
25328
25329    fn generate_convert_to_charset(&mut self, e: &ConvertToCharset) -> Result<()> {
25330        // CONVERT(this USING dest)
25331        self.write_keyword("CONVERT");
25332        self.write("(");
25333        self.generate_expression(&e.this)?;
25334        if let Some(dest) = &e.dest {
25335            self.write_space();
25336            self.write_keyword("USING");
25337            self.write_space();
25338            self.generate_expression(dest)?;
25339        }
25340        self.write(")");
25341        Ok(())
25342    }
25343
25344    fn generate_copy(&mut self, e: &CopyStmt) -> Result<()> {
25345        self.write_keyword("COPY");
25346        if e.is_into {
25347            self.write_space();
25348            self.write_keyword("INTO");
25349        }
25350        self.write_space();
25351
25352        // Generate target table or query (or stage for COPY INTO @stage)
25353        if let Expression::Literal(Literal::String(s)) = &e.this {
25354            if s.starts_with('@') {
25355                self.write(s);
25356            } else {
25357                self.generate_expression(&e.this)?;
25358            }
25359        } else {
25360            self.generate_expression(&e.this)?;
25361        }
25362
25363        // FROM or TO based on kind
25364        if e.kind {
25365            // kind=true means FROM (loading into table)
25366            if self.config.pretty {
25367                self.write_newline();
25368            } else {
25369                self.write_space();
25370            }
25371            self.write_keyword("FROM");
25372            self.write_space();
25373        } else if !e.files.is_empty() {
25374            // kind=false means TO (exporting)
25375            if self.config.pretty {
25376                self.write_newline();
25377            } else {
25378                self.write_space();
25379            }
25380            self.write_keyword("TO");
25381            self.write_space();
25382        }
25383
25384        // Generate source/destination files
25385        for (i, file) in e.files.iter().enumerate() {
25386            if i > 0 {
25387                self.write_space();
25388            }
25389            // For stage references (strings starting with @), output without quotes
25390            if let Expression::Literal(Literal::String(s)) = file {
25391                if s.starts_with('@') {
25392                    self.write(s);
25393                } else {
25394                    self.generate_expression(file)?;
25395                }
25396            } else if let Expression::Identifier(id) = file {
25397                // Backtick-quoted file path (Databricks style: `s3://link`)
25398                if id.quoted {
25399                    self.write("`");
25400                    self.write(&id.name);
25401                    self.write("`");
25402                } else {
25403                    self.generate_expression(file)?;
25404                }
25405            } else {
25406                self.generate_expression(file)?;
25407            }
25408        }
25409
25410        // Generate credentials if present (Snowflake style - not wrapped in WITH)
25411        if !e.with_wrapped {
25412            if let Some(ref creds) = e.credentials {
25413                if let Some(ref storage) = creds.storage {
25414                    if self.config.pretty {
25415                        self.write_newline();
25416                    } else {
25417                        self.write_space();
25418                    }
25419                    self.write_keyword("STORAGE_INTEGRATION");
25420                    self.write(" = ");
25421                    self.write(storage);
25422                }
25423                if creds.credentials.is_empty() {
25424                    // Empty credentials: CREDENTIALS = ()
25425                    if self.config.pretty {
25426                        self.write_newline();
25427                    } else {
25428                        self.write_space();
25429                    }
25430                    self.write_keyword("CREDENTIALS");
25431                    self.write(" = ()");
25432                } else {
25433                    if self.config.pretty {
25434                        self.write_newline();
25435                    } else {
25436                        self.write_space();
25437                    }
25438                    self.write_keyword("CREDENTIALS");
25439                    // Check if this is Redshift-style (single value with empty key)
25440                    // vs Snowflake-style (multiple key=value pairs)
25441                    if creds.credentials.len() == 1 && creds.credentials[0].0.is_empty() {
25442                        // Redshift style: CREDENTIALS 'value'
25443                        self.write(" '");
25444                        self.write(&creds.credentials[0].1);
25445                        self.write("'");
25446                    } else {
25447                        // Snowflake style: CREDENTIALS = (KEY='value' ...)
25448                        self.write(" = (");
25449                        for (i, (k, v)) in creds.credentials.iter().enumerate() {
25450                            if i > 0 {
25451                                self.write_space();
25452                            }
25453                            self.write(k);
25454                            self.write("='");
25455                            self.write(v);
25456                            self.write("'");
25457                        }
25458                        self.write(")");
25459                    }
25460                }
25461                if let Some(ref encryption) = creds.encryption {
25462                    self.write_space();
25463                    self.write_keyword("ENCRYPTION");
25464                    self.write(" = ");
25465                    self.write(encryption);
25466                }
25467            }
25468        }
25469
25470        // Generate parameters
25471        if !e.params.is_empty() {
25472            if e.with_wrapped {
25473                // DuckDB/PostgreSQL/TSQL WITH (...) format
25474                self.write_space();
25475                self.write_keyword("WITH");
25476                self.write(" (");
25477                for (i, param) in e.params.iter().enumerate() {
25478                    if i > 0 {
25479                        self.write(", ");
25480                    }
25481                    self.generate_copy_param_with_format(param)?;
25482                }
25483                self.write(")");
25484            } else {
25485                // Snowflake/Redshift format: KEY = VALUE or KEY VALUE (space separated, no WITH wrapper)
25486                // For Redshift: IAM_ROLE value, CREDENTIALS 'value', REGION 'value', FORMAT type
25487                // For Snowflake: KEY = VALUE
25488                for param in &e.params {
25489                    if self.config.pretty {
25490                        self.write_newline();
25491                    } else {
25492                        self.write_space();
25493                    }
25494                    // Preserve original case of parameter name (important for Redshift COPY options)
25495                    self.write(&param.name);
25496                    if let Some(ref value) = param.value {
25497                        // Use = only if it was present in the original (param.eq)
25498                        if param.eq {
25499                            self.write(" = ");
25500                        } else {
25501                            self.write(" ");
25502                        }
25503                        if !param.values.is_empty() {
25504                            self.write("(");
25505                            for (i, v) in param.values.iter().enumerate() {
25506                                if i > 0 {
25507                                    self.write_space();
25508                                }
25509                                self.generate_copy_nested_param(v)?;
25510                            }
25511                            self.write(")");
25512                        } else {
25513                            // For COPY parameter values, output identifiers without quoting
25514                            self.generate_copy_param_value(value)?;
25515                        }
25516                    } else if !param.values.is_empty() {
25517                        // For varlen options like FORMAT_OPTIONS, COPY_OPTIONS - no = before (
25518                        if param.eq {
25519                            self.write(" = (");
25520                        } else {
25521                            self.write(" (");
25522                        }
25523                        // Determine separator for values inside parentheses:
25524                        // - Snowflake FILE_FORMAT = (TYPE=CSV FIELD_DELIMITER='|') → space-separated (has = before parens)
25525                        // - Databricks FORMAT_OPTIONS ('opt1'='true', 'opt2'='test') → comma-separated (no = before parens)
25526                        // - Simple value lists like FILES = ('file1', 'file2') → comma-separated
25527                        let is_key_value_pairs = param
25528                            .values
25529                            .first()
25530                            .map_or(false, |v| matches!(v, Expression::Eq(_)));
25531                        let sep = if is_key_value_pairs && param.eq {
25532                            " "
25533                        } else {
25534                            ", "
25535                        };
25536                        for (i, v) in param.values.iter().enumerate() {
25537                            if i > 0 {
25538                                self.write(sep);
25539                            }
25540                            self.generate_copy_nested_param(v)?;
25541                        }
25542                        self.write(")");
25543                    }
25544                }
25545            }
25546        }
25547
25548        Ok(())
25549    }
25550
25551    /// Generate a COPY parameter in WITH (...) format
25552    /// Handles both KEY = VALUE (TSQL) and KEY VALUE (DuckDB/PostgreSQL) formats
25553    fn generate_copy_param_with_format(&mut self, param: &CopyParameter) -> Result<()> {
25554        self.write_keyword(&param.name);
25555        if !param.values.is_empty() {
25556            // Nested values: CREDENTIAL = (IDENTITY='...', SECRET='...')
25557            self.write(" = (");
25558            for (i, v) in param.values.iter().enumerate() {
25559                if i > 0 {
25560                    self.write(", ");
25561                }
25562                self.generate_copy_nested_param(v)?;
25563            }
25564            self.write(")");
25565        } else if let Some(ref value) = param.value {
25566            if param.eq {
25567                self.write(" = ");
25568            } else {
25569                self.write(" ");
25570            }
25571            self.generate_expression(value)?;
25572        }
25573        Ok(())
25574    }
25575
25576    /// Generate nested parameter for COPY statements (KEY=VALUE without spaces)
25577    fn generate_copy_nested_param(&mut self, expr: &Expression) -> Result<()> {
25578        match expr {
25579            Expression::Eq(eq) => {
25580                // Generate key
25581                match &eq.left {
25582                    Expression::Column(c) => self.write(&c.name.name),
25583                    _ => self.generate_expression(&eq.left)?,
25584                }
25585                self.write("=");
25586                // Generate value
25587                match &eq.right {
25588                    Expression::Literal(Literal::String(s)) => {
25589                        self.write("'");
25590                        self.write(s);
25591                        self.write("'");
25592                    }
25593                    Expression::Tuple(t) => {
25594                        // For lists like NULL_IF=('', 'str1')
25595                        self.write("(");
25596                        if self.config.pretty {
25597                            self.write_newline();
25598                            self.indent_level += 1;
25599                            for (i, item) in t.expressions.iter().enumerate() {
25600                                if i > 0 {
25601                                    self.write(", ");
25602                                }
25603                                self.write_indent();
25604                                self.generate_expression(item)?;
25605                            }
25606                            self.write_newline();
25607                            self.indent_level -= 1;
25608                        } else {
25609                            for (i, item) in t.expressions.iter().enumerate() {
25610                                if i > 0 {
25611                                    self.write(", ");
25612                                }
25613                                self.generate_expression(item)?;
25614                            }
25615                        }
25616                        self.write(")");
25617                    }
25618                    _ => self.generate_expression(&eq.right)?,
25619                }
25620                Ok(())
25621            }
25622            Expression::Column(c) => {
25623                // Standalone keyword like COMPRESSION
25624                self.write(&c.name.name);
25625                Ok(())
25626            }
25627            _ => self.generate_expression(expr),
25628        }
25629    }
25630
25631    /// Generate a COPY parameter value, outputting identifiers/columns without quoting
25632    /// This is needed for Redshift-style COPY params like: IAM_ROLE default, FORMAT orc
25633    fn generate_copy_param_value(&mut self, expr: &Expression) -> Result<()> {
25634        match expr {
25635            Expression::Column(c) => {
25636                // Output identifier, preserving quotes if originally quoted
25637                if c.name.quoted {
25638                    self.write("\"");
25639                    self.write(&c.name.name);
25640                    self.write("\"");
25641                } else {
25642                    self.write(&c.name.name);
25643                }
25644                Ok(())
25645            }
25646            Expression::Identifier(id) => {
25647                // Output identifier, preserving quotes if originally quoted
25648                if id.quoted {
25649                    self.write("\"");
25650                    self.write(&id.name);
25651                    self.write("\"");
25652                } else {
25653                    self.write(&id.name);
25654                }
25655                Ok(())
25656            }
25657            Expression::Literal(Literal::String(s)) => {
25658                // Output string with quotes
25659                self.write("'");
25660                self.write(s);
25661                self.write("'");
25662                Ok(())
25663            }
25664            _ => self.generate_expression(expr),
25665        }
25666    }
25667
25668    fn generate_copy_parameter(&mut self, e: &CopyParameter) -> Result<()> {
25669        self.write_keyword(&e.name);
25670        if let Some(ref value) = e.value {
25671            if e.eq {
25672                self.write(" = ");
25673            } else {
25674                self.write(" ");
25675            }
25676            self.generate_expression(value)?;
25677        }
25678        if !e.values.is_empty() {
25679            if e.eq {
25680                self.write(" = ");
25681            } else {
25682                self.write(" ");
25683            }
25684            self.write("(");
25685            for (i, v) in e.values.iter().enumerate() {
25686                if i > 0 {
25687                    self.write(", ");
25688                }
25689                self.generate_expression(v)?;
25690            }
25691            self.write(")");
25692        }
25693        Ok(())
25694    }
25695
25696    fn generate_corr(&mut self, e: &Corr) -> Result<()> {
25697        // CORR(this, expression)
25698        self.write_keyword("CORR");
25699        self.write("(");
25700        self.generate_expression(&e.this)?;
25701        self.write(", ");
25702        self.generate_expression(&e.expression)?;
25703        self.write(")");
25704        Ok(())
25705    }
25706
25707    fn generate_cosine_distance(&mut self, e: &CosineDistance) -> Result<()> {
25708        // COSINE_DISTANCE(this, expression)
25709        self.write_keyword("COSINE_DISTANCE");
25710        self.write("(");
25711        self.generate_expression(&e.this)?;
25712        self.write(", ");
25713        self.generate_expression(&e.expression)?;
25714        self.write(")");
25715        Ok(())
25716    }
25717
25718    fn generate_covar_pop(&mut self, e: &CovarPop) -> Result<()> {
25719        // COVAR_POP(this, expression)
25720        self.write_keyword("COVAR_POP");
25721        self.write("(");
25722        self.generate_expression(&e.this)?;
25723        self.write(", ");
25724        self.generate_expression(&e.expression)?;
25725        self.write(")");
25726        Ok(())
25727    }
25728
25729    fn generate_covar_samp(&mut self, e: &CovarSamp) -> Result<()> {
25730        // COVAR_SAMP(this, expression)
25731        self.write_keyword("COVAR_SAMP");
25732        self.write("(");
25733        self.generate_expression(&e.this)?;
25734        self.write(", ");
25735        self.generate_expression(&e.expression)?;
25736        self.write(")");
25737        Ok(())
25738    }
25739
25740    fn generate_credentials(&mut self, e: &Credentials) -> Result<()> {
25741        // CREDENTIALS (key1='value1', key2='value2')
25742        self.write_keyword("CREDENTIALS");
25743        self.write(" (");
25744        for (i, (key, value)) in e.credentials.iter().enumerate() {
25745            if i > 0 {
25746                self.write(", ");
25747            }
25748            self.write(key);
25749            self.write("='");
25750            self.write(value);
25751            self.write("'");
25752        }
25753        self.write(")");
25754        Ok(())
25755    }
25756
25757    fn generate_credentials_property(&mut self, e: &CredentialsProperty) -> Result<()> {
25758        // CREDENTIALS=(expressions)
25759        self.write_keyword("CREDENTIALS");
25760        self.write("=(");
25761        for (i, expr) in e.expressions.iter().enumerate() {
25762            if i > 0 {
25763                self.write(", ");
25764            }
25765            self.generate_expression(expr)?;
25766        }
25767        self.write(")");
25768        Ok(())
25769    }
25770
25771    fn generate_cte(&mut self, e: &Cte) -> Result<()> {
25772        use crate::dialects::DialectType;
25773
25774        // Python: return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
25775        // Output: alias [(col1, col2, ...)] AS [MATERIALIZED|NOT MATERIALIZED] (subquery)
25776        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !e.alias_first {
25777            self.generate_expression(&e.this)?;
25778            self.write_space();
25779            self.write_keyword("AS");
25780            self.write_space();
25781            self.generate_identifier(&e.alias)?;
25782            return Ok(());
25783        }
25784        self.write(&e.alias.name);
25785
25786        // BigQuery doesn't support column aliases in CTE definitions
25787        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
25788
25789        if !e.columns.is_empty() && !skip_cte_columns {
25790            self.write("(");
25791            for (i, col) in e.columns.iter().enumerate() {
25792                if i > 0 {
25793                    self.write(", ");
25794                }
25795                self.write(&col.name);
25796            }
25797            self.write(")");
25798        }
25799        // USING KEY (columns) for DuckDB recursive CTEs
25800        if !e.key_expressions.is_empty() {
25801            self.write_space();
25802            self.write_keyword("USING KEY");
25803            self.write(" (");
25804            for (i, key) in e.key_expressions.iter().enumerate() {
25805                if i > 0 {
25806                    self.write(", ");
25807                }
25808                self.write(&key.name);
25809            }
25810            self.write(")");
25811        }
25812        self.write_space();
25813        self.write_keyword("AS");
25814        self.write_space();
25815        if let Some(materialized) = e.materialized {
25816            if materialized {
25817                self.write_keyword("MATERIALIZED");
25818            } else {
25819                self.write_keyword("NOT MATERIALIZED");
25820            }
25821            self.write_space();
25822        }
25823        self.write("(");
25824        self.generate_expression(&e.this)?;
25825        self.write(")");
25826        Ok(())
25827    }
25828
25829    fn generate_cube(&mut self, e: &Cube) -> Result<()> {
25830        // Python: return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
25831        if e.expressions.is_empty() {
25832            self.write_keyword("WITH CUBE");
25833        } else {
25834            self.write_keyword("CUBE");
25835            self.write("(");
25836            for (i, expr) in e.expressions.iter().enumerate() {
25837                if i > 0 {
25838                    self.write(", ");
25839                }
25840                self.generate_expression(expr)?;
25841            }
25842            self.write(")");
25843        }
25844        Ok(())
25845    }
25846
25847    fn generate_current_datetime(&mut self, e: &CurrentDatetime) -> Result<()> {
25848        // CURRENT_DATETIME or CURRENT_DATETIME(timezone)
25849        self.write_keyword("CURRENT_DATETIME");
25850        if let Some(this) = &e.this {
25851            self.write("(");
25852            self.generate_expression(this)?;
25853            self.write(")");
25854        }
25855        Ok(())
25856    }
25857
25858    fn generate_current_schema(&mut self, _e: &CurrentSchema) -> Result<()> {
25859        // CURRENT_SCHEMA - no arguments
25860        self.write_keyword("CURRENT_SCHEMA");
25861        Ok(())
25862    }
25863
25864    fn generate_current_schemas(&mut self, e: &CurrentSchemas) -> Result<()> {
25865        // CURRENT_SCHEMAS(include_implicit)
25866        self.write_keyword("CURRENT_SCHEMAS");
25867        self.write("(");
25868        if let Some(this) = &e.this {
25869            self.generate_expression(this)?;
25870        }
25871        self.write(")");
25872        Ok(())
25873    }
25874
25875    fn generate_current_user(&mut self, e: &CurrentUser) -> Result<()> {
25876        // CURRENT_USER or CURRENT_USER()
25877        self.write_keyword("CURRENT_USER");
25878        // Some dialects always need parens: Snowflake, Spark, Hive, DuckDB, BigQuery, MySQL, Databricks
25879        let needs_parens = e.this.is_some()
25880            || matches!(
25881                self.config.dialect,
25882                Some(DialectType::Snowflake)
25883                    | Some(DialectType::Spark)
25884                    | Some(DialectType::Hive)
25885                    | Some(DialectType::DuckDB)
25886                    | Some(DialectType::BigQuery)
25887                    | Some(DialectType::MySQL)
25888                    | Some(DialectType::Databricks)
25889            );
25890        if needs_parens {
25891            self.write("()");
25892        }
25893        Ok(())
25894    }
25895
25896    fn generate_d_pipe(&mut self, e: &DPipe) -> Result<()> {
25897        // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
25898        if self.config.dialect == Some(DialectType::Solr) {
25899            self.generate_expression(&e.this)?;
25900            self.write(" ");
25901            self.write_keyword("OR");
25902            self.write(" ");
25903            self.generate_expression(&e.expression)?;
25904        } else {
25905            // String concatenation: this || expression
25906            self.generate_expression(&e.this)?;
25907            self.write(" || ");
25908            self.generate_expression(&e.expression)?;
25909        }
25910        Ok(())
25911    }
25912
25913    fn generate_data_blocksize_property(&mut self, e: &DataBlocksizeProperty) -> Result<()> {
25914        // DATABLOCKSIZE=... (Teradata)
25915        self.write_keyword("DATABLOCKSIZE");
25916        self.write("=");
25917        if let Some(size) = e.size {
25918            self.write(&size.to_string());
25919            if let Some(units) = &e.units {
25920                self.write_space();
25921                self.generate_expression(units)?;
25922            }
25923        } else if e.minimum.is_some() {
25924            self.write_keyword("MINIMUM");
25925        } else if e.maximum.is_some() {
25926            self.write_keyword("MAXIMUM");
25927        } else if e.default.is_some() {
25928            self.write_keyword("DEFAULT");
25929        }
25930        Ok(())
25931    }
25932
25933    fn generate_data_deletion_property(&mut self, e: &DataDeletionProperty) -> Result<()> {
25934        // DATA_DELETION=ON or DATA_DELETION=OFF or DATA_DELETION=ON(FILTER_COLUMN=col, RETENTION_PERIOD=...)
25935        self.write_keyword("DATA_DELETION");
25936        self.write("=");
25937
25938        let is_on = matches!(&*e.on, Expression::Boolean(BooleanLiteral { value: true }));
25939        let has_options = e.filter_column.is_some() || e.retention_period.is_some();
25940
25941        if is_on {
25942            self.write_keyword("ON");
25943            if has_options {
25944                self.write("(");
25945                let mut first = true;
25946                if let Some(filter_column) = &e.filter_column {
25947                    self.write_keyword("FILTER_COLUMN");
25948                    self.write("=");
25949                    self.generate_expression(filter_column)?;
25950                    first = false;
25951                }
25952                if let Some(retention_period) = &e.retention_period {
25953                    if !first {
25954                        self.write(", ");
25955                    }
25956                    self.write_keyword("RETENTION_PERIOD");
25957                    self.write("=");
25958                    self.generate_expression(retention_period)?;
25959                }
25960                self.write(")");
25961            }
25962        } else {
25963            self.write_keyword("OFF");
25964        }
25965        Ok(())
25966    }
25967
25968    /// Generate a Date function expression
25969    /// For Exasol: {d'value'} -> TO_DATE('value')
25970    /// For other dialects: DATE('value')
25971    fn generate_date_func(&mut self, e: &UnaryFunc) -> Result<()> {
25972        use crate::dialects::DialectType;
25973        use crate::expressions::Literal;
25974
25975        match self.config.dialect {
25976            // Exasol uses TO_DATE for Date expressions
25977            Some(DialectType::Exasol) => {
25978                self.write_keyword("TO_DATE");
25979                self.write("(");
25980                // Extract the string value from the expression if it's a string literal
25981                match &e.this {
25982                    Expression::Literal(Literal::String(s)) => {
25983                        self.write("'");
25984                        self.write(s);
25985                        self.write("'");
25986                    }
25987                    _ => {
25988                        self.generate_expression(&e.this)?;
25989                    }
25990                }
25991                self.write(")");
25992            }
25993            // Standard: DATE(value)
25994            _ => {
25995                self.write_keyword("DATE");
25996                self.write("(");
25997                self.generate_expression(&e.this)?;
25998                self.write(")");
25999            }
26000        }
26001        Ok(())
26002    }
26003
26004    fn generate_date_bin(&mut self, e: &DateBin) -> Result<()> {
26005        // DATE_BIN(interval, timestamp[, origin])
26006        self.write_keyword("DATE_BIN");
26007        self.write("(");
26008        self.generate_expression(&e.this)?;
26009        self.write(", ");
26010        self.generate_expression(&e.expression)?;
26011        if let Some(origin) = &e.origin {
26012            self.write(", ");
26013            self.generate_expression(origin)?;
26014        }
26015        self.write(")");
26016        Ok(())
26017    }
26018
26019    fn generate_date_format_column_constraint(
26020        &mut self,
26021        e: &DateFormatColumnConstraint,
26022    ) -> Result<()> {
26023        // FORMAT 'format_string' (Teradata)
26024        self.write_keyword("FORMAT");
26025        self.write_space();
26026        self.generate_expression(&e.this)?;
26027        Ok(())
26028    }
26029
26030    fn generate_date_from_parts(&mut self, e: &DateFromParts) -> Result<()> {
26031        // DATE_FROM_PARTS(year, month, day) or DATEFROMPARTS(year, month, day)
26032        self.write_keyword("DATE_FROM_PARTS");
26033        self.write("(");
26034        let mut first = true;
26035        if let Some(year) = &e.year {
26036            self.generate_expression(year)?;
26037            first = false;
26038        }
26039        if let Some(month) = &e.month {
26040            if !first {
26041                self.write(", ");
26042            }
26043            self.generate_expression(month)?;
26044            first = false;
26045        }
26046        if let Some(day) = &e.day {
26047            if !first {
26048                self.write(", ");
26049            }
26050            self.generate_expression(day)?;
26051        }
26052        self.write(")");
26053        Ok(())
26054    }
26055
26056    fn generate_datetime(&mut self, e: &Datetime) -> Result<()> {
26057        // DATETIME(this) or DATETIME(this, expression)
26058        self.write_keyword("DATETIME");
26059        self.write("(");
26060        self.generate_expression(&e.this)?;
26061        if let Some(expr) = &e.expression {
26062            self.write(", ");
26063            self.generate_expression(expr)?;
26064        }
26065        self.write(")");
26066        Ok(())
26067    }
26068
26069    fn generate_datetime_add(&mut self, e: &DatetimeAdd) -> Result<()> {
26070        // DATETIME_ADD(this, expression, unit)
26071        self.write_keyword("DATETIME_ADD");
26072        self.write("(");
26073        self.generate_expression(&e.this)?;
26074        self.write(", ");
26075        self.generate_expression(&e.expression)?;
26076        if let Some(unit) = &e.unit {
26077            self.write(", ");
26078            self.write_keyword(unit);
26079        }
26080        self.write(")");
26081        Ok(())
26082    }
26083
26084    fn generate_datetime_diff(&mut self, e: &DatetimeDiff) -> Result<()> {
26085        // DATETIME_DIFF(this, expression, unit)
26086        self.write_keyword("DATETIME_DIFF");
26087        self.write("(");
26088        self.generate_expression(&e.this)?;
26089        self.write(", ");
26090        self.generate_expression(&e.expression)?;
26091        if let Some(unit) = &e.unit {
26092            self.write(", ");
26093            self.write_keyword(unit);
26094        }
26095        self.write(")");
26096        Ok(())
26097    }
26098
26099    fn generate_datetime_sub(&mut self, e: &DatetimeSub) -> Result<()> {
26100        // DATETIME_SUB(this, expression, unit)
26101        self.write_keyword("DATETIME_SUB");
26102        self.write("(");
26103        self.generate_expression(&e.this)?;
26104        self.write(", ");
26105        self.generate_expression(&e.expression)?;
26106        if let Some(unit) = &e.unit {
26107            self.write(", ");
26108            self.write_keyword(unit);
26109        }
26110        self.write(")");
26111        Ok(())
26112    }
26113
26114    fn generate_datetime_trunc(&mut self, e: &DatetimeTrunc) -> Result<()> {
26115        // DATETIME_TRUNC(this, unit, zone)
26116        self.write_keyword("DATETIME_TRUNC");
26117        self.write("(");
26118        self.generate_expression(&e.this)?;
26119        self.write(", ");
26120        self.write_keyword(&e.unit);
26121        if let Some(zone) = &e.zone {
26122            self.write(", ");
26123            self.generate_expression(zone)?;
26124        }
26125        self.write(")");
26126        Ok(())
26127    }
26128
26129    fn generate_dayname(&mut self, e: &Dayname) -> Result<()> {
26130        // DAYNAME(this)
26131        self.write_keyword("DAYNAME");
26132        self.write("(");
26133        self.generate_expression(&e.this)?;
26134        self.write(")");
26135        Ok(())
26136    }
26137
26138    fn generate_declare(&mut self, e: &Declare) -> Result<()> {
26139        // DECLARE var1 AS type1, var2 AS type2, ...
26140        self.write_keyword("DECLARE");
26141        self.write_space();
26142        for (i, expr) in e.expressions.iter().enumerate() {
26143            if i > 0 {
26144                self.write(", ");
26145            }
26146            self.generate_expression(expr)?;
26147        }
26148        Ok(())
26149    }
26150
26151    fn generate_declare_item(&mut self, e: &DeclareItem) -> Result<()> {
26152        use crate::dialects::DialectType;
26153
26154        // variable TYPE [DEFAULT default]
26155        self.generate_expression(&e.this)?;
26156        // BigQuery multi-variable: DECLARE X, Y, Z INT64
26157        for name in &e.additional_names {
26158            self.write(", ");
26159            self.generate_expression(name)?;
26160        }
26161        if let Some(kind) = &e.kind {
26162            self.write_space();
26163            // BigQuery uses: DECLARE x INT64 DEFAULT value (no AS)
26164            // TSQL: Always includes AS (normalization)
26165            // Others: Include AS if present in original
26166            match self.config.dialect {
26167                Some(DialectType::BigQuery) => {
26168                    self.write(kind);
26169                }
26170                Some(DialectType::TSQL) => {
26171                    // TSQL: Check for complex TABLE constraints that should be passed through unchanged
26172                    // Python sqlglot falls back to Command for TABLE declarations with CLUSTERED,
26173                    // NONCLUSTERED, or INDEX constraints
26174                    let is_complex_table = kind.starts_with("TABLE")
26175                        && (kind.contains("CLUSTERED") || kind.contains("INDEX"));
26176
26177                    if is_complex_table {
26178                        // Complex TABLE declarations: preserve as-is (no AS, no INT normalization)
26179                        self.write(kind);
26180                    } else {
26181                        // Simple declarations: add AS (except for CURSOR) and normalize INT
26182                        if !kind.starts_with("CURSOR") {
26183                            self.write_keyword("AS");
26184                            self.write_space();
26185                        }
26186                        // Normalize INT to INTEGER for TSQL DECLARE statements
26187                        if kind == "INT" {
26188                            self.write("INTEGER");
26189                        } else if kind.starts_with("TABLE") {
26190                            // Normalize INT to INTEGER inside TABLE column definitions
26191                            let normalized = kind
26192                                .replace(" INT ", " INTEGER ")
26193                                .replace(" INT,", " INTEGER,")
26194                                .replace(" INT)", " INTEGER)")
26195                                .replace("(INT ", "(INTEGER ");
26196                            self.write(&normalized);
26197                        } else {
26198                            self.write(kind);
26199                        }
26200                    }
26201                }
26202                _ => {
26203                    if e.has_as {
26204                        self.write_keyword("AS");
26205                        self.write_space();
26206                    }
26207                    self.write(kind);
26208                }
26209            }
26210        }
26211        if let Some(default) = &e.default {
26212            // BigQuery uses DEFAULT, others use =
26213            match self.config.dialect {
26214                Some(DialectType::BigQuery) => {
26215                    self.write_space();
26216                    self.write_keyword("DEFAULT");
26217                    self.write_space();
26218                }
26219                _ => {
26220                    self.write(" = ");
26221                }
26222            }
26223            self.generate_expression(default)?;
26224        }
26225        Ok(())
26226    }
26227
26228    fn generate_decode_case(&mut self, e: &DecodeCase) -> Result<()> {
26229        // DECODE(expr, search1, result1, search2, result2, ..., default)
26230        self.write_keyword("DECODE");
26231        self.write("(");
26232        for (i, expr) in e.expressions.iter().enumerate() {
26233            if i > 0 {
26234                self.write(", ");
26235            }
26236            self.generate_expression(expr)?;
26237        }
26238        self.write(")");
26239        Ok(())
26240    }
26241
26242    fn generate_decompress_binary(&mut self, e: &DecompressBinary) -> Result<()> {
26243        // DECOMPRESS(expr, 'method')
26244        self.write_keyword("DECOMPRESS");
26245        self.write("(");
26246        self.generate_expression(&e.this)?;
26247        self.write(", '");
26248        self.write(&e.method);
26249        self.write("')");
26250        Ok(())
26251    }
26252
26253    fn generate_decompress_string(&mut self, e: &DecompressString) -> Result<()> {
26254        // DECOMPRESS(expr, 'method')
26255        self.write_keyword("DECOMPRESS");
26256        self.write("(");
26257        self.generate_expression(&e.this)?;
26258        self.write(", '");
26259        self.write(&e.method);
26260        self.write("')");
26261        Ok(())
26262    }
26263
26264    fn generate_decrypt(&mut self, e: &Decrypt) -> Result<()> {
26265        // DECRYPT(value, passphrase [, aad [, algorithm]])
26266        self.write_keyword("DECRYPT");
26267        self.write("(");
26268        self.generate_expression(&e.this)?;
26269        if let Some(passphrase) = &e.passphrase {
26270            self.write(", ");
26271            self.generate_expression(passphrase)?;
26272        }
26273        if let Some(aad) = &e.aad {
26274            self.write(", ");
26275            self.generate_expression(aad)?;
26276        }
26277        if let Some(method) = &e.encryption_method {
26278            self.write(", ");
26279            self.generate_expression(method)?;
26280        }
26281        self.write(")");
26282        Ok(())
26283    }
26284
26285    fn generate_decrypt_raw(&mut self, e: &DecryptRaw) -> Result<()> {
26286        // DECRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
26287        self.write_keyword("DECRYPT_RAW");
26288        self.write("(");
26289        self.generate_expression(&e.this)?;
26290        if let Some(key) = &e.key {
26291            self.write(", ");
26292            self.generate_expression(key)?;
26293        }
26294        if let Some(iv) = &e.iv {
26295            self.write(", ");
26296            self.generate_expression(iv)?;
26297        }
26298        if let Some(aad) = &e.aad {
26299            self.write(", ");
26300            self.generate_expression(aad)?;
26301        }
26302        if let Some(method) = &e.encryption_method {
26303            self.write(", ");
26304            self.generate_expression(method)?;
26305        }
26306        self.write(")");
26307        Ok(())
26308    }
26309
26310    fn generate_definer_property(&mut self, e: &DefinerProperty) -> Result<()> {
26311        // DEFINER = user
26312        self.write_keyword("DEFINER");
26313        self.write(" = ");
26314        self.generate_expression(&e.this)?;
26315        Ok(())
26316    }
26317
26318    fn generate_detach(&mut self, e: &Detach) -> Result<()> {
26319        // Python: DETACH[DATABASE IF EXISTS] this
26320        self.write_keyword("DETACH");
26321        if e.exists {
26322            self.write_keyword(" DATABASE IF EXISTS");
26323        }
26324        self.write_space();
26325        self.generate_expression(&e.this)?;
26326        Ok(())
26327    }
26328
26329    fn generate_dict_property(&mut self, e: &DictProperty) -> Result<()> {
26330        let property_name = match e.this.as_ref() {
26331            Expression::Identifier(id) => id.name.as_str(),
26332            Expression::Var(v) => v.this.as_str(),
26333            _ => "DICTIONARY",
26334        };
26335        self.write_keyword(property_name);
26336        self.write("(");
26337        self.write(&e.kind);
26338        if let Some(settings) = &e.settings {
26339            self.write("(");
26340            if let Expression::Tuple(t) = settings.as_ref() {
26341                if self.config.pretty && !t.expressions.is_empty() {
26342                    self.write_newline();
26343                    self.indent_level += 1;
26344                    for (i, pair) in t.expressions.iter().enumerate() {
26345                        if i > 0 {
26346                            self.write(",");
26347                            self.write_newline();
26348                        }
26349                        self.write_indent();
26350                        if let Expression::Tuple(pair_tuple) = pair {
26351                            if let Some(k) = pair_tuple.expressions.first() {
26352                                self.generate_expression(k)?;
26353                            }
26354                            if let Some(v) = pair_tuple.expressions.get(1) {
26355                                self.write(" ");
26356                                self.generate_expression(v)?;
26357                            }
26358                        } else {
26359                            self.generate_expression(pair)?;
26360                        }
26361                    }
26362                    self.indent_level -= 1;
26363                    self.write_newline();
26364                    self.write_indent();
26365                } else {
26366                    for (i, pair) in t.expressions.iter().enumerate() {
26367                        if i > 0 {
26368                            self.write(", ");
26369                        }
26370                        if let Expression::Tuple(pair_tuple) = pair {
26371                            if let Some(k) = pair_tuple.expressions.first() {
26372                                self.generate_expression(k)?;
26373                            }
26374                            if let Some(v) = pair_tuple.expressions.get(1) {
26375                                self.write(" ");
26376                                self.generate_expression(v)?;
26377                            }
26378                        } else {
26379                            self.generate_expression(pair)?;
26380                        }
26381                    }
26382                }
26383            } else {
26384                self.generate_expression(settings)?;
26385            }
26386            self.write(")");
26387        } else if property_name.eq_ignore_ascii_case("LAYOUT") {
26388            self.write("()");
26389        }
26390        self.write(")");
26391        Ok(())
26392    }
26393
26394    fn generate_dict_range(&mut self, e: &DictRange) -> Result<()> {
26395        let property_name = match e.this.as_ref() {
26396            Expression::Identifier(id) => id.name.as_str(),
26397            Expression::Var(v) => v.this.as_str(),
26398            _ => "RANGE",
26399        };
26400        self.write_keyword(property_name);
26401        self.write("(");
26402        if let Some(min) = &e.min {
26403            self.write_keyword("MIN");
26404            self.write_space();
26405            self.generate_expression(min)?;
26406        }
26407        if let Some(max) = &e.max {
26408            self.write_space();
26409            self.write_keyword("MAX");
26410            self.write_space();
26411            self.generate_expression(max)?;
26412        }
26413        self.write(")");
26414        Ok(())
26415    }
26416
26417    fn generate_directory(&mut self, e: &Directory) -> Result<()> {
26418        // Python: {local}DIRECTORY {this}{row_format}
26419        if e.local.is_some() {
26420            self.write_keyword("LOCAL ");
26421        }
26422        self.write_keyword("DIRECTORY");
26423        self.write_space();
26424        self.generate_expression(&e.this)?;
26425        if let Some(row_format) = &e.row_format {
26426            self.write_space();
26427            self.generate_expression(row_format)?;
26428        }
26429        Ok(())
26430    }
26431
26432    fn generate_dist_key_property(&mut self, e: &DistKeyProperty) -> Result<()> {
26433        // Redshift: DISTKEY(column)
26434        self.write_keyword("DISTKEY");
26435        self.write("(");
26436        self.generate_expression(&e.this)?;
26437        self.write(")");
26438        Ok(())
26439    }
26440
26441    fn generate_dist_style_property(&mut self, e: &DistStyleProperty) -> Result<()> {
26442        // Redshift: DISTSTYLE KEY|ALL|EVEN|AUTO
26443        self.write_keyword("DISTSTYLE");
26444        self.write_space();
26445        self.generate_expression(&e.this)?;
26446        Ok(())
26447    }
26448
26449    fn generate_distribute_by(&mut self, e: &DistributeBy) -> Result<()> {
26450        // Python: "DISTRIBUTE BY" expressions
26451        self.write_keyword("DISTRIBUTE BY");
26452        self.write_space();
26453        for (i, expr) in e.expressions.iter().enumerate() {
26454            if i > 0 {
26455                self.write(", ");
26456            }
26457            self.generate_expression(expr)?;
26458        }
26459        Ok(())
26460    }
26461
26462    fn generate_distributed_by_property(&mut self, e: &DistributedByProperty) -> Result<()> {
26463        // Python: DISTRIBUTED BY kind (expressions) BUCKETS buckets order
26464        self.write_keyword("DISTRIBUTED BY");
26465        self.write_space();
26466        self.write(&e.kind);
26467        if !e.expressions.is_empty() {
26468            self.write(" (");
26469            for (i, expr) in e.expressions.iter().enumerate() {
26470                if i > 0 {
26471                    self.write(", ");
26472                }
26473                self.generate_expression(expr)?;
26474            }
26475            self.write(")");
26476        }
26477        if let Some(buckets) = &e.buckets {
26478            self.write_space();
26479            self.write_keyword("BUCKETS");
26480            self.write_space();
26481            self.generate_expression(buckets)?;
26482        }
26483        if let Some(order) = &e.order {
26484            self.write_space();
26485            self.generate_expression(order)?;
26486        }
26487        Ok(())
26488    }
26489
26490    fn generate_dot_product(&mut self, e: &DotProduct) -> Result<()> {
26491        // DOT_PRODUCT(vector1, vector2)
26492        self.write_keyword("DOT_PRODUCT");
26493        self.write("(");
26494        self.generate_expression(&e.this)?;
26495        self.write(", ");
26496        self.generate_expression(&e.expression)?;
26497        self.write(")");
26498        Ok(())
26499    }
26500
26501    fn generate_drop_partition(&mut self, e: &DropPartition) -> Result<()> {
26502        // Python: DROP{IF EXISTS }expressions
26503        self.write_keyword("DROP");
26504        if e.exists {
26505            self.write_keyword(" IF EXISTS ");
26506        } else {
26507            self.write_space();
26508        }
26509        for (i, expr) in e.expressions.iter().enumerate() {
26510            if i > 0 {
26511                self.write(", ");
26512            }
26513            self.generate_expression(expr)?;
26514        }
26515        Ok(())
26516    }
26517
26518    fn generate_duplicate_key_property(&mut self, e: &DuplicateKeyProperty) -> Result<()> {
26519        // Python: DUPLICATE KEY (expressions)
26520        self.write_keyword("DUPLICATE KEY");
26521        self.write(" (");
26522        for (i, expr) in e.expressions.iter().enumerate() {
26523            if i > 0 {
26524                self.write(", ");
26525            }
26526            self.generate_expression(expr)?;
26527        }
26528        self.write(")");
26529        Ok(())
26530    }
26531
26532    fn generate_elt(&mut self, e: &Elt) -> Result<()> {
26533        // ELT(index, str1, str2, ...)
26534        self.write_keyword("ELT");
26535        self.write("(");
26536        self.generate_expression(&e.this)?;
26537        for expr in &e.expressions {
26538            self.write(", ");
26539            self.generate_expression(expr)?;
26540        }
26541        self.write(")");
26542        Ok(())
26543    }
26544
26545    fn generate_encode(&mut self, e: &Encode) -> Result<()> {
26546        // ENCODE(string, charset)
26547        self.write_keyword("ENCODE");
26548        self.write("(");
26549        self.generate_expression(&e.this)?;
26550        if let Some(charset) = &e.charset {
26551            self.write(", ");
26552            self.generate_expression(charset)?;
26553        }
26554        self.write(")");
26555        Ok(())
26556    }
26557
26558    fn generate_encode_property(&mut self, e: &EncodeProperty) -> Result<()> {
26559        // Python: [KEY ]ENCODE this [properties]
26560        if e.key.is_some() {
26561            self.write_keyword("KEY ");
26562        }
26563        self.write_keyword("ENCODE");
26564        self.write_space();
26565        self.generate_expression(&e.this)?;
26566        if !e.properties.is_empty() {
26567            self.write(" (");
26568            for (i, prop) in e.properties.iter().enumerate() {
26569                if i > 0 {
26570                    self.write(", ");
26571                }
26572                self.generate_expression(prop)?;
26573            }
26574            self.write(")");
26575        }
26576        Ok(())
26577    }
26578
26579    fn generate_encrypt(&mut self, e: &Encrypt) -> Result<()> {
26580        // ENCRYPT(value, passphrase [, aad [, algorithm]])
26581        self.write_keyword("ENCRYPT");
26582        self.write("(");
26583        self.generate_expression(&e.this)?;
26584        if let Some(passphrase) = &e.passphrase {
26585            self.write(", ");
26586            self.generate_expression(passphrase)?;
26587        }
26588        if let Some(aad) = &e.aad {
26589            self.write(", ");
26590            self.generate_expression(aad)?;
26591        }
26592        if let Some(method) = &e.encryption_method {
26593            self.write(", ");
26594            self.generate_expression(method)?;
26595        }
26596        self.write(")");
26597        Ok(())
26598    }
26599
26600    fn generate_encrypt_raw(&mut self, e: &EncryptRaw) -> Result<()> {
26601        // ENCRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
26602        self.write_keyword("ENCRYPT_RAW");
26603        self.write("(");
26604        self.generate_expression(&e.this)?;
26605        if let Some(key) = &e.key {
26606            self.write(", ");
26607            self.generate_expression(key)?;
26608        }
26609        if let Some(iv) = &e.iv {
26610            self.write(", ");
26611            self.generate_expression(iv)?;
26612        }
26613        if let Some(aad) = &e.aad {
26614            self.write(", ");
26615            self.generate_expression(aad)?;
26616        }
26617        if let Some(method) = &e.encryption_method {
26618            self.write(", ");
26619            self.generate_expression(method)?;
26620        }
26621        self.write(")");
26622        Ok(())
26623    }
26624
26625    fn generate_engine_property(&mut self, e: &EngineProperty) -> Result<()> {
26626        // MySQL: ENGINE = InnoDB
26627        self.write_keyword("ENGINE");
26628        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
26629            self.write("=");
26630        } else {
26631            self.write(" = ");
26632        }
26633        self.generate_expression(&e.this)?;
26634        Ok(())
26635    }
26636
26637    fn generate_enviroment_property(&mut self, e: &EnviromentProperty) -> Result<()> {
26638        // ENVIRONMENT (expressions)
26639        self.write_keyword("ENVIRONMENT");
26640        self.write(" (");
26641        for (i, expr) in e.expressions.iter().enumerate() {
26642            if i > 0 {
26643                self.write(", ");
26644            }
26645            self.generate_expression(expr)?;
26646        }
26647        self.write(")");
26648        Ok(())
26649    }
26650
26651    fn generate_ephemeral_column_constraint(
26652        &mut self,
26653        e: &EphemeralColumnConstraint,
26654    ) -> Result<()> {
26655        // MySQL: EPHEMERAL [expr]
26656        self.write_keyword("EPHEMERAL");
26657        if let Some(this) = &e.this {
26658            self.write_space();
26659            self.generate_expression(this)?;
26660        }
26661        Ok(())
26662    }
26663
26664    fn generate_equal_null(&mut self, e: &EqualNull) -> Result<()> {
26665        // Snowflake: EQUAL_NULL(a, b)
26666        self.write_keyword("EQUAL_NULL");
26667        self.write("(");
26668        self.generate_expression(&e.this)?;
26669        self.write(", ");
26670        self.generate_expression(&e.expression)?;
26671        self.write(")");
26672        Ok(())
26673    }
26674
26675    fn generate_euclidean_distance(&mut self, e: &EuclideanDistance) -> Result<()> {
26676        use crate::dialects::DialectType;
26677
26678        // PostgreSQL uses <-> operator syntax
26679        match self.config.dialect {
26680            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
26681                self.generate_expression(&e.this)?;
26682                self.write(" <-> ");
26683                self.generate_expression(&e.expression)?;
26684            }
26685            _ => {
26686                // Other dialects use EUCLIDEAN_DISTANCE function
26687                self.write_keyword("EUCLIDEAN_DISTANCE");
26688                self.write("(");
26689                self.generate_expression(&e.this)?;
26690                self.write(", ");
26691                self.generate_expression(&e.expression)?;
26692                self.write(")");
26693            }
26694        }
26695        Ok(())
26696    }
26697
26698    fn generate_execute_as_property(&mut self, e: &ExecuteAsProperty) -> Result<()> {
26699        // EXECUTE AS CALLER|OWNER|user
26700        self.write_keyword("EXECUTE AS");
26701        self.write_space();
26702        self.generate_expression(&e.this)?;
26703        Ok(())
26704    }
26705
26706    fn generate_export(&mut self, e: &Export) -> Result<()> {
26707        // BigQuery: EXPORT DATA [WITH CONNECTION connection] OPTIONS (...) AS query
26708        self.write_keyword("EXPORT DATA");
26709        if let Some(connection) = &e.connection {
26710            self.write_space();
26711            self.write_keyword("WITH CONNECTION");
26712            self.write_space();
26713            self.generate_expression(connection)?;
26714        }
26715        if !e.options.is_empty() {
26716            self.write_space();
26717            self.generate_options_clause(&e.options)?;
26718        }
26719        self.write_space();
26720        self.write_keyword("AS");
26721        self.write_space();
26722        self.generate_expression(&e.this)?;
26723        Ok(())
26724    }
26725
26726    fn generate_external_property(&mut self, e: &ExternalProperty) -> Result<()> {
26727        // EXTERNAL [this]
26728        self.write_keyword("EXTERNAL");
26729        if let Some(this) = &e.this {
26730            self.write_space();
26731            self.generate_expression(this)?;
26732        }
26733        Ok(())
26734    }
26735
26736    fn generate_fallback_property(&mut self, e: &FallbackProperty) -> Result<()> {
26737        // Python: {no}FALLBACK{protection}
26738        if e.no.is_some() {
26739            self.write_keyword("NO ");
26740        }
26741        self.write_keyword("FALLBACK");
26742        if e.protection.is_some() {
26743            self.write_keyword(" PROTECTION");
26744        }
26745        Ok(())
26746    }
26747
26748    fn generate_farm_fingerprint(&mut self, e: &FarmFingerprint) -> Result<()> {
26749        // BigQuery: FARM_FINGERPRINT(value)
26750        self.write_keyword("FARM_FINGERPRINT");
26751        self.write("(");
26752        for (i, expr) in e.expressions.iter().enumerate() {
26753            if i > 0 {
26754                self.write(", ");
26755            }
26756            self.generate_expression(expr)?;
26757        }
26758        self.write(")");
26759        Ok(())
26760    }
26761
26762    fn generate_features_at_time(&mut self, e: &FeaturesAtTime) -> Result<()> {
26763        // BigQuery ML: FEATURES_AT_TIME(feature_view, time, [num_rows], [ignore_feature_nulls])
26764        self.write_keyword("FEATURES_AT_TIME");
26765        self.write("(");
26766        self.generate_expression(&e.this)?;
26767        if let Some(time) = &e.time {
26768            self.write(", ");
26769            self.generate_expression(time)?;
26770        }
26771        if let Some(num_rows) = &e.num_rows {
26772            self.write(", ");
26773            self.generate_expression(num_rows)?;
26774        }
26775        if let Some(ignore_nulls) = &e.ignore_feature_nulls {
26776            self.write(", ");
26777            self.generate_expression(ignore_nulls)?;
26778        }
26779        self.write(")");
26780        Ok(())
26781    }
26782
26783    fn generate_fetch(&mut self, e: &Fetch) -> Result<()> {
26784        // For dialects that prefer LIMIT, convert simple FETCH to LIMIT
26785        let use_limit = !e.percent
26786            && !e.with_ties
26787            && e.count.is_some()
26788            && matches!(
26789                self.config.dialect,
26790                Some(DialectType::Spark)
26791                    | Some(DialectType::Hive)
26792                    | Some(DialectType::DuckDB)
26793                    | Some(DialectType::SQLite)
26794                    | Some(DialectType::MySQL)
26795                    | Some(DialectType::BigQuery)
26796                    | Some(DialectType::Databricks)
26797                    | Some(DialectType::StarRocks)
26798                    | Some(DialectType::Doris)
26799                    | Some(DialectType::Athena)
26800                    | Some(DialectType::ClickHouse)
26801            );
26802
26803        if use_limit {
26804            self.write_keyword("LIMIT");
26805            self.write_space();
26806            self.generate_expression(e.count.as_ref().unwrap())?;
26807            return Ok(());
26808        }
26809
26810        // Python: FETCH direction count limit_options
26811        self.write_keyword("FETCH");
26812        if !e.direction.is_empty() {
26813            self.write_space();
26814            self.write_keyword(&e.direction);
26815        }
26816        if let Some(count) = &e.count {
26817            self.write_space();
26818            self.generate_expression(count)?;
26819        }
26820        // Generate PERCENT, ROWS, WITH TIES/ONLY
26821        if e.percent {
26822            self.write_keyword(" PERCENT");
26823        }
26824        if e.rows {
26825            self.write_keyword(" ROWS");
26826        }
26827        if e.with_ties {
26828            self.write_keyword(" WITH TIES");
26829        } else if e.rows {
26830            self.write_keyword(" ONLY");
26831        } else {
26832            self.write_keyword(" ROWS ONLY");
26833        }
26834        Ok(())
26835    }
26836
26837    fn generate_file_format_property(&mut self, e: &FileFormatProperty) -> Result<()> {
26838        // For Hive format: STORED AS this or STORED AS INPUTFORMAT x OUTPUTFORMAT y
26839        // For Spark/Databricks without hive_format: USING this
26840        // For Snowflake/others: FILE_FORMAT = this or FILE_FORMAT = (expressions)
26841        if e.hive_format.is_some() {
26842            // Hive format: STORED AS ...
26843            self.write_keyword("STORED AS");
26844            self.write_space();
26845            if let Some(this) = &e.this {
26846                // Uppercase the format name (e.g., parquet -> PARQUET)
26847                if let Expression::Identifier(id) = this.as_ref() {
26848                    self.write_keyword(&id.name.to_uppercase());
26849                } else {
26850                    self.generate_expression(this)?;
26851                }
26852            }
26853        } else if matches!(self.config.dialect, Some(DialectType::Hive)) {
26854            // Hive: STORED AS format
26855            self.write_keyword("STORED AS");
26856            self.write_space();
26857            if let Some(this) = &e.this {
26858                if let Expression::Identifier(id) = this.as_ref() {
26859                    self.write_keyword(&id.name.to_uppercase());
26860                } else {
26861                    self.generate_expression(this)?;
26862                }
26863            }
26864        } else if matches!(
26865            self.config.dialect,
26866            Some(DialectType::Spark) | Some(DialectType::Databricks)
26867        ) {
26868            // Spark/Databricks: USING format (e.g., USING DELTA)
26869            self.write_keyword("USING");
26870            self.write_space();
26871            if let Some(this) = &e.this {
26872                self.generate_expression(this)?;
26873            }
26874        } else {
26875            // Snowflake/standard format
26876            self.write_keyword("FILE_FORMAT");
26877            self.write(" = ");
26878            if let Some(this) = &e.this {
26879                self.generate_expression(this)?;
26880            } else if !e.expressions.is_empty() {
26881                self.write("(");
26882                for (i, expr) in e.expressions.iter().enumerate() {
26883                    if i > 0 {
26884                        self.write(", ");
26885                    }
26886                    self.generate_expression(expr)?;
26887                }
26888                self.write(")");
26889            }
26890        }
26891        Ok(())
26892    }
26893
26894    fn generate_filter(&mut self, e: &Filter) -> Result<()> {
26895        // agg_func FILTER(WHERE condition)
26896        self.generate_expression(&e.this)?;
26897        self.write_space();
26898        self.write_keyword("FILTER");
26899        self.write("(");
26900        self.write_keyword("WHERE");
26901        self.write_space();
26902        self.generate_expression(&e.expression)?;
26903        self.write(")");
26904        Ok(())
26905    }
26906
26907    fn generate_float64(&mut self, e: &Float64) -> Result<()> {
26908        // FLOAT64(this) or FLOAT64(this, expression)
26909        self.write_keyword("FLOAT64");
26910        self.write("(");
26911        self.generate_expression(&e.this)?;
26912        if let Some(expr) = &e.expression {
26913            self.write(", ");
26914            self.generate_expression(expr)?;
26915        }
26916        self.write(")");
26917        Ok(())
26918    }
26919
26920    fn generate_for_in(&mut self, e: &ForIn) -> Result<()> {
26921        // FOR this DO expression
26922        self.write_keyword("FOR");
26923        self.write_space();
26924        self.generate_expression(&e.this)?;
26925        self.write_space();
26926        self.write_keyword("DO");
26927        self.write_space();
26928        self.generate_expression(&e.expression)?;
26929        Ok(())
26930    }
26931
26932    fn generate_foreign_key(&mut self, e: &ForeignKey) -> Result<()> {
26933        // FOREIGN KEY (cols) REFERENCES table(cols) ON DELETE action ON UPDATE action
26934        self.write_keyword("FOREIGN KEY");
26935        if !e.expressions.is_empty() {
26936            self.write(" (");
26937            for (i, expr) in e.expressions.iter().enumerate() {
26938                if i > 0 {
26939                    self.write(", ");
26940                }
26941                self.generate_expression(expr)?;
26942            }
26943            self.write(")");
26944        }
26945        if let Some(reference) = &e.reference {
26946            self.write_space();
26947            self.generate_expression(reference)?;
26948        }
26949        if let Some(delete) = &e.delete {
26950            self.write_space();
26951            self.write_keyword("ON DELETE");
26952            self.write_space();
26953            self.generate_expression(delete)?;
26954        }
26955        if let Some(update) = &e.update {
26956            self.write_space();
26957            self.write_keyword("ON UPDATE");
26958            self.write_space();
26959            self.generate_expression(update)?;
26960        }
26961        if !e.options.is_empty() {
26962            self.write_space();
26963            for (i, opt) in e.options.iter().enumerate() {
26964                if i > 0 {
26965                    self.write_space();
26966                }
26967                self.generate_expression(opt)?;
26968            }
26969        }
26970        Ok(())
26971    }
26972
26973    fn generate_format(&mut self, e: &Format) -> Result<()> {
26974        // FORMAT(this, expressions...)
26975        self.write_keyword("FORMAT");
26976        self.write("(");
26977        self.generate_expression(&e.this)?;
26978        for expr in &e.expressions {
26979            self.write(", ");
26980            self.generate_expression(expr)?;
26981        }
26982        self.write(")");
26983        Ok(())
26984    }
26985
26986    fn generate_format_phrase(&mut self, e: &FormatPhrase) -> Result<()> {
26987        // Teradata: column (FORMAT 'format_string')
26988        self.generate_expression(&e.this)?;
26989        self.write(" (");
26990        self.write_keyword("FORMAT");
26991        self.write(" '");
26992        self.write(&e.format);
26993        self.write("')");
26994        Ok(())
26995    }
26996
26997    fn generate_freespace_property(&mut self, e: &FreespaceProperty) -> Result<()> {
26998        // Python: FREESPACE=this[PERCENT]
26999        self.write_keyword("FREESPACE");
27000        self.write("=");
27001        self.generate_expression(&e.this)?;
27002        if e.percent.is_some() {
27003            self.write_keyword(" PERCENT");
27004        }
27005        Ok(())
27006    }
27007
27008    fn generate_from(&mut self, e: &From) -> Result<()> {
27009        // Python: return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
27010        self.write_keyword("FROM");
27011        self.write_space();
27012
27013        // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax
27014        // But keep commas when TABLESAMPLE is present
27015        // Also keep commas when the source dialect is Generic/None and target is one of these dialects
27016        use crate::dialects::DialectType;
27017        let has_tablesample = e
27018            .expressions
27019            .iter()
27020            .any(|expr| matches!(expr, Expression::TableSample(_)));
27021        let is_cross_join_dialect = matches!(
27022            self.config.dialect,
27023            Some(DialectType::BigQuery)
27024                | Some(DialectType::Hive)
27025                | Some(DialectType::Spark)
27026                | Some(DialectType::Databricks)
27027                | Some(DialectType::SQLite)
27028                | Some(DialectType::ClickHouse)
27029        );
27030        let source_is_same_as_target2 = self.config.source_dialect.is_some()
27031            && self.config.source_dialect == self.config.dialect;
27032        let source_is_cross_join_dialect2 = matches!(
27033            self.config.source_dialect,
27034            Some(DialectType::BigQuery)
27035                | Some(DialectType::Hive)
27036                | Some(DialectType::Spark)
27037                | Some(DialectType::Databricks)
27038                | Some(DialectType::SQLite)
27039                | Some(DialectType::ClickHouse)
27040        );
27041        let use_cross_join = !has_tablesample
27042            && is_cross_join_dialect
27043            && (source_is_same_as_target2
27044                || source_is_cross_join_dialect2
27045                || self.config.source_dialect.is_none());
27046
27047        // Snowflake wraps standalone VALUES in FROM clause with parentheses
27048        let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
27049
27050        for (i, expr) in e.expressions.iter().enumerate() {
27051            if i > 0 {
27052                if use_cross_join {
27053                    self.write(" CROSS JOIN ");
27054                } else {
27055                    self.write(", ");
27056                }
27057            }
27058            if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
27059                self.write("(");
27060                self.generate_expression(expr)?;
27061                self.write(")");
27062            } else {
27063                self.generate_expression(expr)?;
27064            }
27065        }
27066        Ok(())
27067    }
27068
27069    fn generate_from_base(&mut self, e: &FromBase) -> Result<()> {
27070        // FROM_BASE(this, expression) - convert from base N
27071        self.write_keyword("FROM_BASE");
27072        self.write("(");
27073        self.generate_expression(&e.this)?;
27074        self.write(", ");
27075        self.generate_expression(&e.expression)?;
27076        self.write(")");
27077        Ok(())
27078    }
27079
27080    fn generate_from_time_zone(&mut self, e: &FromTimeZone) -> Result<()> {
27081        // this AT TIME ZONE zone AT TIME ZONE 'UTC'
27082        self.generate_expression(&e.this)?;
27083        if let Some(zone) = &e.zone {
27084            self.write_space();
27085            self.write_keyword("AT TIME ZONE");
27086            self.write_space();
27087            self.generate_expression(zone)?;
27088            self.write_space();
27089            self.write_keyword("AT TIME ZONE");
27090            self.write(" 'UTC'");
27091        }
27092        Ok(())
27093    }
27094
27095    fn generate_gap_fill(&mut self, e: &GapFill) -> Result<()> {
27096        // GAP_FILL(this, ts_column, bucket_width, ...)
27097        self.write_keyword("GAP_FILL");
27098        self.write("(");
27099        self.generate_expression(&e.this)?;
27100        if let Some(ts_column) = &e.ts_column {
27101            self.write(", ");
27102            self.generate_expression(ts_column)?;
27103        }
27104        if let Some(bucket_width) = &e.bucket_width {
27105            self.write(", ");
27106            self.generate_expression(bucket_width)?;
27107        }
27108        if let Some(partitioning_columns) = &e.partitioning_columns {
27109            self.write(", ");
27110            self.generate_expression(partitioning_columns)?;
27111        }
27112        if let Some(value_columns) = &e.value_columns {
27113            self.write(", ");
27114            self.generate_expression(value_columns)?;
27115        }
27116        self.write(")");
27117        Ok(())
27118    }
27119
27120    fn generate_generate_date_array(&mut self, e: &GenerateDateArray) -> Result<()> {
27121        // GENERATE_DATE_ARRAY(start, end, step)
27122        self.write_keyword("GENERATE_DATE_ARRAY");
27123        self.write("(");
27124        let mut first = true;
27125        if let Some(start) = &e.start {
27126            self.generate_expression(start)?;
27127            first = false;
27128        }
27129        if let Some(end) = &e.end {
27130            if !first {
27131                self.write(", ");
27132            }
27133            self.generate_expression(end)?;
27134            first = false;
27135        }
27136        if let Some(step) = &e.step {
27137            if !first {
27138                self.write(", ");
27139            }
27140            self.generate_expression(step)?;
27141        }
27142        self.write(")");
27143        Ok(())
27144    }
27145
27146    fn generate_generate_embedding(&mut self, e: &GenerateEmbedding) -> Result<()> {
27147        // ML.GENERATE_EMBEDDING(model, content, params)
27148        self.write_keyword("ML.GENERATE_EMBEDDING");
27149        self.write("(");
27150        self.generate_expression(&e.this)?;
27151        self.write(", ");
27152        self.generate_expression(&e.expression)?;
27153        if let Some(params) = &e.params_struct {
27154            self.write(", ");
27155            self.generate_expression(params)?;
27156        }
27157        self.write(")");
27158        Ok(())
27159    }
27160
27161    fn generate_generate_series(&mut self, e: &GenerateSeries) -> Result<()> {
27162        // Dialect-specific function name
27163        let fn_name = match self.config.dialect {
27164            Some(DialectType::Presto)
27165            | Some(DialectType::Trino)
27166            | Some(DialectType::Athena)
27167            | Some(DialectType::Spark)
27168            | Some(DialectType::Databricks)
27169            | Some(DialectType::Hive) => "SEQUENCE",
27170            _ => "GENERATE_SERIES",
27171        };
27172        self.write_keyword(fn_name);
27173        self.write("(");
27174        let mut first = true;
27175        if let Some(start) = &e.start {
27176            self.generate_expression(start)?;
27177            first = false;
27178        }
27179        if let Some(end) = &e.end {
27180            if !first {
27181                self.write(", ");
27182            }
27183            self.generate_expression(end)?;
27184            first = false;
27185        }
27186        if let Some(step) = &e.step {
27187            if !first {
27188                self.write(", ");
27189            }
27190            // For Presto/Trino: convert WEEK intervals to DAY multiples
27191            // e.g., INTERVAL '1' WEEK -> (1 * INTERVAL '7' DAY)
27192            if matches!(
27193                self.config.dialect,
27194                Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
27195            ) {
27196                if let Some(converted) = self.convert_week_interval_to_day(step) {
27197                    self.generate_expression(&converted)?;
27198                } else {
27199                    self.generate_expression(step)?;
27200                }
27201            } else {
27202                self.generate_expression(step)?;
27203            }
27204        }
27205        self.write(")");
27206        Ok(())
27207    }
27208
27209    /// Convert a WEEK interval to a DAY-based multiplication expression for Presto/Trino.
27210    /// INTERVAL N WEEK -> (N * INTERVAL '7' DAY)
27211    fn convert_week_interval_to_day(&self, expr: &Expression) -> Option<Expression> {
27212        use crate::expressions::*;
27213        if let Expression::Interval(ref iv) = expr {
27214            // Check for structured WEEK unit
27215            let (is_week, count_str) = if let Some(IntervalUnitSpec::Simple {
27216                unit: IntervalUnit::Week,
27217                ..
27218            }) = &iv.unit
27219            {
27220                // Value is in iv.this
27221                let count = match &iv.this {
27222                    Some(Expression::Literal(Literal::String(s))) => s.clone(),
27223                    Some(Expression::Literal(Literal::Number(s))) => s.clone(),
27224                    _ => return None,
27225                };
27226                (true, count)
27227            } else if iv.unit.is_none() {
27228                // Check for string-encoded interval like "1 WEEK"
27229                if let Some(Expression::Literal(Literal::String(s))) = &iv.this {
27230                    let parts: Vec<&str> = s.trim().splitn(2, char::is_whitespace).collect();
27231                    if parts.len() == 2 && parts[1].eq_ignore_ascii_case("WEEK") {
27232                        (true, parts[0].to_string())
27233                    } else {
27234                        (false, String::new())
27235                    }
27236                } else {
27237                    (false, String::new())
27238                }
27239            } else {
27240                (false, String::new())
27241            };
27242
27243            if is_week {
27244                // Build: (N * INTERVAL '7' DAY)
27245                let count_expr = Expression::Literal(Literal::Number(count_str));
27246                let day_interval = Expression::Interval(Box::new(Interval {
27247                    this: Some(Expression::Literal(Literal::String("7".to_string()))),
27248                    unit: Some(IntervalUnitSpec::Simple {
27249                        unit: IntervalUnit::Day,
27250                        use_plural: false,
27251                    }),
27252                }));
27253                let mul = Expression::Mul(Box::new(BinaryOp {
27254                    left: count_expr,
27255                    right: day_interval,
27256                    left_comments: vec![],
27257                    operator_comments: vec![],
27258                    trailing_comments: vec![],
27259                }));
27260                return Some(Expression::Paren(Box::new(Paren {
27261                    this: mul,
27262                    trailing_comments: vec![],
27263                })));
27264            }
27265        }
27266        None
27267    }
27268
27269    fn generate_generate_timestamp_array(&mut self, e: &GenerateTimestampArray) -> Result<()> {
27270        // GENERATE_TIMESTAMP_ARRAY(start, end, step)
27271        self.write_keyword("GENERATE_TIMESTAMP_ARRAY");
27272        self.write("(");
27273        let mut first = true;
27274        if let Some(start) = &e.start {
27275            self.generate_expression(start)?;
27276            first = false;
27277        }
27278        if let Some(end) = &e.end {
27279            if !first {
27280                self.write(", ");
27281            }
27282            self.generate_expression(end)?;
27283            first = false;
27284        }
27285        if let Some(step) = &e.step {
27286            if !first {
27287                self.write(", ");
27288            }
27289            self.generate_expression(step)?;
27290        }
27291        self.write(")");
27292        Ok(())
27293    }
27294
27295    fn generate_generated_as_identity_column_constraint(
27296        &mut self,
27297        e: &GeneratedAsIdentityColumnConstraint,
27298    ) -> Result<()> {
27299        use crate::dialects::DialectType;
27300
27301        // For Snowflake, use AUTOINCREMENT START x INCREMENT y syntax
27302        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
27303            self.write_keyword("AUTOINCREMENT");
27304            if let Some(start) = &e.start {
27305                self.write_keyword(" START ");
27306                self.generate_expression(start)?;
27307            }
27308            if let Some(increment) = &e.increment {
27309                self.write_keyword(" INCREMENT ");
27310                self.generate_expression(increment)?;
27311            }
27312            return Ok(());
27313        }
27314
27315        // Python: GENERATED [ALWAYS|BY DEFAULT [ON NULL]] AS IDENTITY [(start, increment, ...)]
27316        self.write_keyword("GENERATED");
27317        if let Some(this) = &e.this {
27318            // Check if it's a truthy boolean expression
27319            if let Expression::Boolean(b) = this.as_ref() {
27320                if b.value {
27321                    self.write_keyword(" ALWAYS");
27322                } else {
27323                    self.write_keyword(" BY DEFAULT");
27324                    if e.on_null.is_some() {
27325                        self.write_keyword(" ON NULL");
27326                    }
27327                }
27328            } else {
27329                self.write_keyword(" ALWAYS");
27330            }
27331        }
27332        self.write_keyword(" AS IDENTITY");
27333        // Add sequence options if any
27334        let has_options = e.start.is_some()
27335            || e.increment.is_some()
27336            || e.minvalue.is_some()
27337            || e.maxvalue.is_some();
27338        if has_options {
27339            self.write(" (");
27340            let mut first = true;
27341            if let Some(start) = &e.start {
27342                self.write_keyword("START WITH ");
27343                self.generate_expression(start)?;
27344                first = false;
27345            }
27346            if let Some(increment) = &e.increment {
27347                if !first {
27348                    self.write(" ");
27349                }
27350                self.write_keyword("INCREMENT BY ");
27351                self.generate_expression(increment)?;
27352                first = false;
27353            }
27354            if let Some(minvalue) = &e.minvalue {
27355                if !first {
27356                    self.write(" ");
27357                }
27358                self.write_keyword("MINVALUE ");
27359                self.generate_expression(minvalue)?;
27360                first = false;
27361            }
27362            if let Some(maxvalue) = &e.maxvalue {
27363                if !first {
27364                    self.write(" ");
27365                }
27366                self.write_keyword("MAXVALUE ");
27367                self.generate_expression(maxvalue)?;
27368            }
27369            self.write(")");
27370        }
27371        Ok(())
27372    }
27373
27374    fn generate_generated_as_row_column_constraint(
27375        &mut self,
27376        e: &GeneratedAsRowColumnConstraint,
27377    ) -> Result<()> {
27378        // Python: GENERATED ALWAYS AS ROW START|END [HIDDEN]
27379        self.write_keyword("GENERATED ALWAYS AS ROW ");
27380        if e.start.is_some() {
27381            self.write_keyword("START");
27382        } else {
27383            self.write_keyword("END");
27384        }
27385        if e.hidden.is_some() {
27386            self.write_keyword(" HIDDEN");
27387        }
27388        Ok(())
27389    }
27390
27391    fn generate_get(&mut self, e: &Get) -> Result<()> {
27392        // GET this target properties
27393        self.write_keyword("GET");
27394        self.write_space();
27395        self.generate_expression(&e.this)?;
27396        if let Some(target) = &e.target {
27397            self.write_space();
27398            self.generate_expression(target)?;
27399        }
27400        for prop in &e.properties {
27401            self.write_space();
27402            self.generate_expression(prop)?;
27403        }
27404        Ok(())
27405    }
27406
27407    fn generate_get_extract(&mut self, e: &GetExtract) -> Result<()> {
27408        // GetExtract generates bracket access: this[expression]
27409        self.generate_expression(&e.this)?;
27410        self.write("[");
27411        self.generate_expression(&e.expression)?;
27412        self.write("]");
27413        Ok(())
27414    }
27415
27416    fn generate_getbit(&mut self, e: &Getbit) -> Result<()> {
27417        // GETBIT(this, expression) or GET_BIT(this, expression)
27418        self.write_keyword("GETBIT");
27419        self.write("(");
27420        self.generate_expression(&e.this)?;
27421        self.write(", ");
27422        self.generate_expression(&e.expression)?;
27423        self.write(")");
27424        Ok(())
27425    }
27426
27427    fn generate_grant_principal(&mut self, e: &GrantPrincipal) -> Result<()> {
27428        // [ROLE|GROUP] name (e.g., "ROLE admin", "GROUP qa_users", or just "user1")
27429        if e.is_role {
27430            self.write_keyword("ROLE");
27431            self.write_space();
27432        } else if e.is_group {
27433            self.write_keyword("GROUP");
27434            self.write_space();
27435        }
27436        self.write(&e.name.name);
27437        Ok(())
27438    }
27439
27440    fn generate_grant_privilege(&mut self, e: &GrantPrivilege) -> Result<()> {
27441        // privilege(columns) or just privilege
27442        self.generate_expression(&e.this)?;
27443        if !e.expressions.is_empty() {
27444            self.write("(");
27445            for (i, expr) in e.expressions.iter().enumerate() {
27446                if i > 0 {
27447                    self.write(", ");
27448                }
27449                self.generate_expression(expr)?;
27450            }
27451            self.write(")");
27452        }
27453        Ok(())
27454    }
27455
27456    fn generate_group(&mut self, e: &Group) -> Result<()> {
27457        // Python handles GROUP BY ALL/DISTINCT modifiers and grouping expressions
27458        self.write_keyword("GROUP BY");
27459        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
27460        match e.all {
27461            Some(true) => {
27462                self.write_space();
27463                self.write_keyword("ALL");
27464            }
27465            Some(false) => {
27466                self.write_space();
27467                self.write_keyword("DISTINCT");
27468            }
27469            None => {}
27470        }
27471        if !e.expressions.is_empty() {
27472            self.write_space();
27473            for (i, expr) in e.expressions.iter().enumerate() {
27474                if i > 0 {
27475                    self.write(", ");
27476                }
27477                self.generate_expression(expr)?;
27478            }
27479        }
27480        // Handle CUBE, ROLLUP, GROUPING SETS
27481        if let Some(cube) = &e.cube {
27482            if !e.expressions.is_empty() {
27483                self.write(", ");
27484            } else {
27485                self.write_space();
27486            }
27487            self.generate_expression(cube)?;
27488        }
27489        if let Some(rollup) = &e.rollup {
27490            if !e.expressions.is_empty() || e.cube.is_some() {
27491                self.write(", ");
27492            } else {
27493                self.write_space();
27494            }
27495            self.generate_expression(rollup)?;
27496        }
27497        if let Some(grouping_sets) = &e.grouping_sets {
27498            if !e.expressions.is_empty() || e.cube.is_some() || e.rollup.is_some() {
27499                self.write(", ");
27500            } else {
27501                self.write_space();
27502            }
27503            self.generate_expression(grouping_sets)?;
27504        }
27505        if let Some(totals) = &e.totals {
27506            self.write_space();
27507            self.write_keyword("WITH TOTALS");
27508            self.generate_expression(totals)?;
27509        }
27510        Ok(())
27511    }
27512
27513    fn generate_group_by(&mut self, e: &GroupBy) -> Result<()> {
27514        // GROUP BY expressions
27515        self.write_keyword("GROUP BY");
27516        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
27517        match e.all {
27518            Some(true) => {
27519                self.write_space();
27520                self.write_keyword("ALL");
27521            }
27522            Some(false) => {
27523                self.write_space();
27524                self.write_keyword("DISTINCT");
27525            }
27526            None => {}
27527        }
27528
27529        // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
27530        // These are represented as Cube/Rollup expressions with empty expressions at the end
27531        let mut trailing_cube = false;
27532        let mut trailing_rollup = false;
27533        let mut regular_expressions: Vec<&Expression> = Vec::new();
27534
27535        for expr in &e.expressions {
27536            match expr {
27537                Expression::Cube(c) if c.expressions.is_empty() => {
27538                    trailing_cube = true;
27539                }
27540                Expression::Rollup(r) if r.expressions.is_empty() => {
27541                    trailing_rollup = true;
27542                }
27543                _ => {
27544                    regular_expressions.push(expr);
27545                }
27546            }
27547        }
27548
27549        // In pretty mode, put columns on separate lines
27550        if self.config.pretty {
27551            self.write_newline();
27552            self.indent_level += 1;
27553            for (i, expr) in regular_expressions.iter().enumerate() {
27554                if i > 0 {
27555                    self.write(",");
27556                    self.write_newline();
27557                }
27558                self.write_indent();
27559                self.generate_expression(expr)?;
27560            }
27561            self.indent_level -= 1;
27562        } else {
27563            self.write_space();
27564            for (i, expr) in regular_expressions.iter().enumerate() {
27565                if i > 0 {
27566                    self.write(", ");
27567                }
27568                self.generate_expression(expr)?;
27569            }
27570        }
27571
27572        // Output trailing WITH CUBE or WITH ROLLUP
27573        if trailing_cube {
27574            self.write_space();
27575            self.write_keyword("WITH CUBE");
27576        } else if trailing_rollup {
27577            self.write_space();
27578            self.write_keyword("WITH ROLLUP");
27579        }
27580
27581        // ClickHouse: WITH TOTALS
27582        if e.totals {
27583            self.write_space();
27584            self.write_keyword("WITH TOTALS");
27585        }
27586
27587        Ok(())
27588    }
27589
27590    fn generate_grouping(&mut self, e: &Grouping) -> Result<()> {
27591        // GROUPING(col1, col2, ...)
27592        self.write_keyword("GROUPING");
27593        self.write("(");
27594        for (i, expr) in e.expressions.iter().enumerate() {
27595            if i > 0 {
27596                self.write(", ");
27597            }
27598            self.generate_expression(expr)?;
27599        }
27600        self.write(")");
27601        Ok(())
27602    }
27603
27604    fn generate_grouping_id(&mut self, e: &GroupingId) -> Result<()> {
27605        // GROUPING_ID(col1, col2, ...)
27606        self.write_keyword("GROUPING_ID");
27607        self.write("(");
27608        for (i, expr) in e.expressions.iter().enumerate() {
27609            if i > 0 {
27610                self.write(", ");
27611            }
27612            self.generate_expression(expr)?;
27613        }
27614        self.write(")");
27615        Ok(())
27616    }
27617
27618    fn generate_grouping_sets(&mut self, e: &GroupingSets) -> Result<()> {
27619        // Python: return f"GROUPING SETS {self.wrap(grouping_sets)}"
27620        self.write_keyword("GROUPING SETS");
27621        self.write(" (");
27622        for (i, expr) in e.expressions.iter().enumerate() {
27623            if i > 0 {
27624                self.write(", ");
27625            }
27626            self.generate_expression(expr)?;
27627        }
27628        self.write(")");
27629        Ok(())
27630    }
27631
27632    fn generate_hash_agg(&mut self, e: &HashAgg) -> Result<()> {
27633        // HASH_AGG(this, expressions...)
27634        self.write_keyword("HASH_AGG");
27635        self.write("(");
27636        self.generate_expression(&e.this)?;
27637        for expr in &e.expressions {
27638            self.write(", ");
27639            self.generate_expression(expr)?;
27640        }
27641        self.write(")");
27642        Ok(())
27643    }
27644
27645    fn generate_having(&mut self, e: &Having) -> Result<()> {
27646        // Python: return f"{self.seg('HAVING')}{self.sep()}{this}"
27647        self.write_keyword("HAVING");
27648        self.write_space();
27649        self.generate_expression(&e.this)?;
27650        Ok(())
27651    }
27652
27653    fn generate_having_max(&mut self, e: &HavingMax) -> Result<()> {
27654        // Python: this HAVING MAX|MIN expression
27655        self.generate_expression(&e.this)?;
27656        self.write_space();
27657        self.write_keyword("HAVING");
27658        self.write_space();
27659        if e.max.is_some() {
27660            self.write_keyword("MAX");
27661        } else {
27662            self.write_keyword("MIN");
27663        }
27664        self.write_space();
27665        self.generate_expression(&e.expression)?;
27666        Ok(())
27667    }
27668
27669    fn generate_heredoc(&mut self, e: &Heredoc) -> Result<()> {
27670        use crate::dialects::DialectType;
27671        // DuckDB: convert dollar-tagged strings to single-quoted
27672        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
27673            // Extract the string content and output as single-quoted
27674            if let Expression::Literal(Literal::String(ref s)) = *e.this {
27675                return self.generate_string_literal(s);
27676            }
27677        }
27678        // PostgreSQL: preserve dollar-quoting
27679        if matches!(
27680            self.config.dialect,
27681            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
27682        ) {
27683            self.write("$");
27684            if let Some(tag) = &e.tag {
27685                self.generate_expression(tag)?;
27686            }
27687            self.write("$");
27688            self.generate_expression(&e.this)?;
27689            self.write("$");
27690            if let Some(tag) = &e.tag {
27691                self.generate_expression(tag)?;
27692            }
27693            self.write("$");
27694            return Ok(());
27695        }
27696        // Default: output as dollar-tagged
27697        self.write("$");
27698        if let Some(tag) = &e.tag {
27699            self.generate_expression(tag)?;
27700        }
27701        self.write("$");
27702        self.generate_expression(&e.this)?;
27703        self.write("$");
27704        if let Some(tag) = &e.tag {
27705            self.generate_expression(tag)?;
27706        }
27707        self.write("$");
27708        Ok(())
27709    }
27710
27711    fn generate_hex_encode(&mut self, e: &HexEncode) -> Result<()> {
27712        // HEX_ENCODE(this)
27713        self.write_keyword("HEX_ENCODE");
27714        self.write("(");
27715        self.generate_expression(&e.this)?;
27716        self.write(")");
27717        Ok(())
27718    }
27719
27720    fn generate_historical_data(&mut self, e: &HistoricalData) -> Result<()> {
27721        // Python: this (kind => expression)
27722        // Write the keyword (AT/BEFORE/END) directly to avoid quoting it as a reserved word
27723        match e.this.as_ref() {
27724            Expression::Identifier(id) => self.write(&id.name),
27725            other => self.generate_expression(other)?,
27726        }
27727        self.write(" (");
27728        self.write(&e.kind);
27729        self.write(" => ");
27730        self.generate_expression(&e.expression)?;
27731        self.write(")");
27732        Ok(())
27733    }
27734
27735    fn generate_hll(&mut self, e: &Hll) -> Result<()> {
27736        // HLL(this, expressions...)
27737        self.write_keyword("HLL");
27738        self.write("(");
27739        self.generate_expression(&e.this)?;
27740        for expr in &e.expressions {
27741            self.write(", ");
27742            self.generate_expression(expr)?;
27743        }
27744        self.write(")");
27745        Ok(())
27746    }
27747
27748    fn generate_in_out_column_constraint(&mut self, e: &InOutColumnConstraint) -> Result<()> {
27749        // Python: IN|OUT|IN OUT
27750        if e.input_.is_some() && e.output.is_some() {
27751            self.write_keyword("IN OUT");
27752        } else if e.input_.is_some() {
27753            self.write_keyword("IN");
27754        } else if e.output.is_some() {
27755            self.write_keyword("OUT");
27756        }
27757        Ok(())
27758    }
27759
27760    fn generate_include_property(&mut self, e: &IncludeProperty) -> Result<()> {
27761        // Python: INCLUDE this [column_def] [AS alias]
27762        self.write_keyword("INCLUDE");
27763        self.write_space();
27764        self.generate_expression(&e.this)?;
27765        if let Some(column_def) = &e.column_def {
27766            self.write_space();
27767            self.generate_expression(column_def)?;
27768        }
27769        if let Some(alias) = &e.alias {
27770            self.write_space();
27771            self.write_keyword("AS");
27772            self.write_space();
27773            self.write(alias);
27774        }
27775        Ok(())
27776    }
27777
27778    fn generate_index(&mut self, e: &Index) -> Result<()> {
27779        // [UNIQUE] [PRIMARY] [AMP] INDEX [name] [ON table] (params)
27780        if e.unique {
27781            self.write_keyword("UNIQUE");
27782            self.write_space();
27783        }
27784        if e.primary.is_some() {
27785            self.write_keyword("PRIMARY");
27786            self.write_space();
27787        }
27788        if e.amp.is_some() {
27789            self.write_keyword("AMP");
27790            self.write_space();
27791        }
27792        if e.table.is_none() {
27793            self.write_keyword("INDEX");
27794            self.write_space();
27795        }
27796        if let Some(name) = &e.this {
27797            self.generate_expression(name)?;
27798            self.write_space();
27799        }
27800        if let Some(table) = &e.table {
27801            self.write_keyword("ON");
27802            self.write_space();
27803            self.generate_expression(table)?;
27804        }
27805        if !e.params.is_empty() {
27806            self.write("(");
27807            for (i, param) in e.params.iter().enumerate() {
27808                if i > 0 {
27809                    self.write(", ");
27810                }
27811                self.generate_expression(param)?;
27812            }
27813            self.write(")");
27814        }
27815        Ok(())
27816    }
27817
27818    fn generate_index_column_constraint(&mut self, e: &IndexColumnConstraint) -> Result<()> {
27819        // Python: kind INDEX [this] [USING index_type] (expressions) [options]
27820        if let Some(kind) = &e.kind {
27821            self.write(kind);
27822            self.write_space();
27823        }
27824        self.write_keyword("INDEX");
27825        if let Some(this) = &e.this {
27826            self.write_space();
27827            self.generate_expression(this)?;
27828        }
27829        if let Some(index_type) = &e.index_type {
27830            self.write_space();
27831            self.write_keyword("USING");
27832            self.write_space();
27833            self.generate_expression(index_type)?;
27834        }
27835        if !e.expressions.is_empty() {
27836            self.write(" (");
27837            for (i, expr) in e.expressions.iter().enumerate() {
27838                if i > 0 {
27839                    self.write(", ");
27840                }
27841                self.generate_expression(expr)?;
27842            }
27843            self.write(")");
27844        }
27845        for opt in &e.options {
27846            self.write_space();
27847            self.generate_expression(opt)?;
27848        }
27849        Ok(())
27850    }
27851
27852    fn generate_index_constraint_option(&mut self, e: &IndexConstraintOption) -> Result<()> {
27853        // Python: KEY_BLOCK_SIZE = x | USING x | WITH PARSER x | COMMENT x | visible | engine_attr | secondary_engine_attr
27854        if let Some(key_block_size) = &e.key_block_size {
27855            self.write_keyword("KEY_BLOCK_SIZE");
27856            self.write(" = ");
27857            self.generate_expression(key_block_size)?;
27858        } else if let Some(using) = &e.using {
27859            self.write_keyword("USING");
27860            self.write_space();
27861            self.generate_expression(using)?;
27862        } else if let Some(parser) = &e.parser {
27863            self.write_keyword("WITH PARSER");
27864            self.write_space();
27865            self.generate_expression(parser)?;
27866        } else if let Some(comment) = &e.comment {
27867            self.write_keyword("COMMENT");
27868            self.write_space();
27869            self.generate_expression(comment)?;
27870        } else if let Some(visible) = &e.visible {
27871            self.generate_expression(visible)?;
27872        } else if let Some(engine_attr) = &e.engine_attr {
27873            self.write_keyword("ENGINE_ATTRIBUTE");
27874            self.write(" = ");
27875            self.generate_expression(engine_attr)?;
27876        } else if let Some(secondary_engine_attr) = &e.secondary_engine_attr {
27877            self.write_keyword("SECONDARY_ENGINE_ATTRIBUTE");
27878            self.write(" = ");
27879            self.generate_expression(secondary_engine_attr)?;
27880        }
27881        Ok(())
27882    }
27883
27884    fn generate_index_parameters(&mut self, e: &IndexParameters) -> Result<()> {
27885        // Python: [USING using] (columns) [PARTITION BY partition_by] [where] [INCLUDE (include)] [WITH (with_storage)] [USING INDEX TABLESPACE tablespace]
27886        if let Some(using) = &e.using {
27887            self.write_keyword("USING");
27888            self.write_space();
27889            self.generate_expression(using)?;
27890        }
27891        if !e.columns.is_empty() {
27892            self.write("(");
27893            for (i, col) in e.columns.iter().enumerate() {
27894                if i > 0 {
27895                    self.write(", ");
27896                }
27897                self.generate_expression(col)?;
27898            }
27899            self.write(")");
27900        }
27901        if let Some(partition_by) = &e.partition_by {
27902            self.write_space();
27903            self.write_keyword("PARTITION BY");
27904            self.write_space();
27905            self.generate_expression(partition_by)?;
27906        }
27907        if let Some(where_) = &e.where_ {
27908            self.write_space();
27909            self.generate_expression(where_)?;
27910        }
27911        if let Some(include) = &e.include {
27912            self.write_space();
27913            self.write_keyword("INCLUDE");
27914            self.write(" (");
27915            self.generate_expression(include)?;
27916            self.write(")");
27917        }
27918        if let Some(with_storage) = &e.with_storage {
27919            self.write_space();
27920            self.write_keyword("WITH");
27921            self.write(" (");
27922            self.generate_expression(with_storage)?;
27923            self.write(")");
27924        }
27925        if let Some(tablespace) = &e.tablespace {
27926            self.write_space();
27927            self.write_keyword("USING INDEX TABLESPACE");
27928            self.write_space();
27929            self.generate_expression(tablespace)?;
27930        }
27931        Ok(())
27932    }
27933
27934    fn generate_index_table_hint(&mut self, e: &IndexTableHint) -> Result<()> {
27935        // Python: this INDEX [FOR target] (expressions)
27936        // Write hint type (USE/IGNORE/FORCE) as keyword, not through generate_expression
27937        // to avoid quoting reserved keywords like IGNORE, FORCE, JOIN
27938        if let Expression::Identifier(id) = &*e.this {
27939            self.write_keyword(&id.name);
27940        } else {
27941            self.generate_expression(&e.this)?;
27942        }
27943        self.write_space();
27944        self.write_keyword("INDEX");
27945        if let Some(target) = &e.target {
27946            self.write_space();
27947            self.write_keyword("FOR");
27948            self.write_space();
27949            if let Expression::Identifier(id) = &**target {
27950                self.write_keyword(&id.name);
27951            } else {
27952                self.generate_expression(target)?;
27953            }
27954        }
27955        // Always output parentheses (even if empty, e.g. USE INDEX ())
27956        self.write(" (");
27957        for (i, expr) in e.expressions.iter().enumerate() {
27958            if i > 0 {
27959                self.write(", ");
27960            }
27961            self.generate_expression(expr)?;
27962        }
27963        self.write(")");
27964        Ok(())
27965    }
27966
27967    fn generate_inherits_property(&mut self, e: &InheritsProperty) -> Result<()> {
27968        // INHERITS (table1, table2, ...)
27969        self.write_keyword("INHERITS");
27970        self.write(" (");
27971        for (i, expr) in e.expressions.iter().enumerate() {
27972            if i > 0 {
27973                self.write(", ");
27974            }
27975            self.generate_expression(expr)?;
27976        }
27977        self.write(")");
27978        Ok(())
27979    }
27980
27981    fn generate_input_model_property(&mut self, e: &InputModelProperty) -> Result<()> {
27982        // INPUT(model)
27983        self.write_keyword("INPUT");
27984        self.write("(");
27985        self.generate_expression(&e.this)?;
27986        self.write(")");
27987        Ok(())
27988    }
27989
27990    fn generate_input_output_format(&mut self, e: &InputOutputFormat) -> Result<()> {
27991        // Python: INPUTFORMAT input_format OUTPUTFORMAT output_format
27992        if let Some(input_format) = &e.input_format {
27993            self.write_keyword("INPUTFORMAT");
27994            self.write_space();
27995            self.generate_expression(input_format)?;
27996        }
27997        if let Some(output_format) = &e.output_format {
27998            if e.input_format.is_some() {
27999                self.write(" ");
28000            }
28001            self.write_keyword("OUTPUTFORMAT");
28002            self.write_space();
28003            self.generate_expression(output_format)?;
28004        }
28005        Ok(())
28006    }
28007
28008    fn generate_install(&mut self, e: &Install) -> Result<()> {
28009        // [FORCE] INSTALL extension [FROM source]
28010        if e.force.is_some() {
28011            self.write_keyword("FORCE");
28012            self.write_space();
28013        }
28014        self.write_keyword("INSTALL");
28015        self.write_space();
28016        self.generate_expression(&e.this)?;
28017        if let Some(from) = &e.from_ {
28018            self.write_space();
28019            self.write_keyword("FROM");
28020            self.write_space();
28021            self.generate_expression(from)?;
28022        }
28023        Ok(())
28024    }
28025
28026    fn generate_interval_op(&mut self, e: &IntervalOp) -> Result<()> {
28027        // INTERVAL 'expression' unit
28028        self.write_keyword("INTERVAL");
28029        self.write_space();
28030        // When a unit is specified and the expression is a number,
28031        self.generate_expression(&e.expression)?;
28032        if let Some(unit) = &e.unit {
28033            self.write_space();
28034            self.write(unit);
28035        }
28036        Ok(())
28037    }
28038
28039    fn generate_interval_span(&mut self, e: &IntervalSpan) -> Result<()> {
28040        // unit TO unit (e.g., HOUR TO SECOND)
28041        self.write(&format!("{:?}", e.this).to_uppercase());
28042        self.write_space();
28043        self.write_keyword("TO");
28044        self.write_space();
28045        self.write(&format!("{:?}", e.expression).to_uppercase());
28046        Ok(())
28047    }
28048
28049    fn generate_into_clause(&mut self, e: &IntoClause) -> Result<()> {
28050        // INTO [TEMPORARY|UNLOGGED] table
28051        self.write_keyword("INTO");
28052        if e.temporary {
28053            self.write_keyword(" TEMPORARY");
28054        }
28055        if e.unlogged.is_some() {
28056            self.write_keyword(" UNLOGGED");
28057        }
28058        if let Some(this) = &e.this {
28059            self.write_space();
28060            self.generate_expression(this)?;
28061        }
28062        if !e.expressions.is_empty() {
28063            self.write(" (");
28064            for (i, expr) in e.expressions.iter().enumerate() {
28065                if i > 0 {
28066                    self.write(", ");
28067                }
28068                self.generate_expression(expr)?;
28069            }
28070            self.write(")");
28071        }
28072        Ok(())
28073    }
28074
28075    fn generate_introducer(&mut self, e: &Introducer) -> Result<()> {
28076        // Python: this expression (e.g., _utf8 'string')
28077        self.generate_expression(&e.this)?;
28078        self.write_space();
28079        self.generate_expression(&e.expression)?;
28080        Ok(())
28081    }
28082
28083    fn generate_isolated_loading_property(&mut self, e: &IsolatedLoadingProperty) -> Result<()> {
28084        // Python: WITH [NO] [CONCURRENT] ISOLATED LOADING [target]
28085        self.write_keyword("WITH");
28086        if e.no.is_some() {
28087            self.write_keyword(" NO");
28088        }
28089        if e.concurrent.is_some() {
28090            self.write_keyword(" CONCURRENT");
28091        }
28092        self.write_keyword(" ISOLATED LOADING");
28093        if let Some(target) = &e.target {
28094            self.write_space();
28095            self.generate_expression(target)?;
28096        }
28097        Ok(())
28098    }
28099
28100    fn generate_json(&mut self, e: &JSON) -> Result<()> {
28101        // Python: JSON [this] [WITHOUT|WITH] [UNIQUE KEYS]
28102        self.write_keyword("JSON");
28103        if let Some(this) = &e.this {
28104            self.write_space();
28105            self.generate_expression(this)?;
28106        }
28107        if let Some(with_) = &e.with_ {
28108            // Check if it's a truthy boolean
28109            if let Expression::Boolean(b) = with_.as_ref() {
28110                if b.value {
28111                    self.write_keyword(" WITH");
28112                } else {
28113                    self.write_keyword(" WITHOUT");
28114                }
28115            }
28116        }
28117        if e.unique {
28118            self.write_keyword(" UNIQUE KEYS");
28119        }
28120        Ok(())
28121    }
28122
28123    fn generate_json_array(&mut self, e: &JSONArray) -> Result<()> {
28124        // Python: return self.func("JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})")
28125        self.write_keyword("JSON_ARRAY");
28126        self.write("(");
28127        for (i, expr) in e.expressions.iter().enumerate() {
28128            if i > 0 {
28129                self.write(", ");
28130            }
28131            self.generate_expression(expr)?;
28132        }
28133        if let Some(null_handling) = &e.null_handling {
28134            self.write_space();
28135            self.generate_expression(null_handling)?;
28136        }
28137        if let Some(return_type) = &e.return_type {
28138            self.write_space();
28139            self.write_keyword("RETURNING");
28140            self.write_space();
28141            self.generate_expression(return_type)?;
28142        }
28143        if e.strict.is_some() {
28144            self.write_space();
28145            self.write_keyword("STRICT");
28146        }
28147        self.write(")");
28148        Ok(())
28149    }
28150
28151    fn generate_json_array_agg_struct(&mut self, e: &JSONArrayAgg) -> Result<()> {
28152        // JSON_ARRAYAGG(this [ORDER BY ...] [NULL ON NULL | ABSENT ON NULL] [RETURNING type] [STRICT])
28153        self.write_keyword("JSON_ARRAYAGG");
28154        self.write("(");
28155        self.generate_expression(&e.this)?;
28156        if let Some(order) = &e.order {
28157            self.write_space();
28158            // Order is stored as an OrderBy expression
28159            if let Expression::OrderBy(ob) = order.as_ref() {
28160                self.write_keyword("ORDER BY");
28161                self.write_space();
28162                for (i, ord) in ob.expressions.iter().enumerate() {
28163                    if i > 0 {
28164                        self.write(", ");
28165                    }
28166                    self.generate_ordered(ord)?;
28167                }
28168            } else {
28169                // Fallback: generate the expression directly
28170                self.generate_expression(order)?;
28171            }
28172        }
28173        if let Some(null_handling) = &e.null_handling {
28174            self.write_space();
28175            self.generate_expression(null_handling)?;
28176        }
28177        if let Some(return_type) = &e.return_type {
28178            self.write_space();
28179            self.write_keyword("RETURNING");
28180            self.write_space();
28181            self.generate_expression(return_type)?;
28182        }
28183        if e.strict.is_some() {
28184            self.write_space();
28185            self.write_keyword("STRICT");
28186        }
28187        self.write(")");
28188        Ok(())
28189    }
28190
28191    fn generate_json_object_agg_struct(&mut self, e: &JSONObjectAgg) -> Result<()> {
28192        // JSON_OBJECTAGG(key: value [NULL ON NULL | ABSENT ON NULL] [WITH UNIQUE KEYS] [RETURNING type])
28193        self.write_keyword("JSON_OBJECTAGG");
28194        self.write("(");
28195        for (i, expr) in e.expressions.iter().enumerate() {
28196            if i > 0 {
28197                self.write(", ");
28198            }
28199            self.generate_expression(expr)?;
28200        }
28201        if let Some(null_handling) = &e.null_handling {
28202            self.write_space();
28203            self.generate_expression(null_handling)?;
28204        }
28205        if let Some(unique_keys) = &e.unique_keys {
28206            self.write_space();
28207            if let Expression::Boolean(b) = unique_keys.as_ref() {
28208                if b.value {
28209                    self.write_keyword("WITH UNIQUE KEYS");
28210                } else {
28211                    self.write_keyword("WITHOUT UNIQUE KEYS");
28212                }
28213            }
28214        }
28215        if let Some(return_type) = &e.return_type {
28216            self.write_space();
28217            self.write_keyword("RETURNING");
28218            self.write_space();
28219            self.generate_expression(return_type)?;
28220        }
28221        self.write(")");
28222        Ok(())
28223    }
28224
28225    fn generate_json_array_append(&mut self, e: &JSONArrayAppend) -> Result<()> {
28226        // JSON_ARRAY_APPEND(this, path, value, ...)
28227        self.write_keyword("JSON_ARRAY_APPEND");
28228        self.write("(");
28229        self.generate_expression(&e.this)?;
28230        for expr in &e.expressions {
28231            self.write(", ");
28232            self.generate_expression(expr)?;
28233        }
28234        self.write(")");
28235        Ok(())
28236    }
28237
28238    fn generate_json_array_contains(&mut self, e: &JSONArrayContains) -> Result<()> {
28239        // JSON_ARRAY_CONTAINS(this, expression)
28240        self.write_keyword("JSON_ARRAY_CONTAINS");
28241        self.write("(");
28242        self.generate_expression(&e.this)?;
28243        self.write(", ");
28244        self.generate_expression(&e.expression)?;
28245        self.write(")");
28246        Ok(())
28247    }
28248
28249    fn generate_json_array_insert(&mut self, e: &JSONArrayInsert) -> Result<()> {
28250        // JSON_ARRAY_INSERT(this, path, value, ...)
28251        self.write_keyword("JSON_ARRAY_INSERT");
28252        self.write("(");
28253        self.generate_expression(&e.this)?;
28254        for expr in &e.expressions {
28255            self.write(", ");
28256            self.generate_expression(expr)?;
28257        }
28258        self.write(")");
28259        Ok(())
28260    }
28261
28262    fn generate_jsonb_exists(&mut self, e: &JSONBExists) -> Result<()> {
28263        // JSONB_EXISTS(this, path)
28264        self.write_keyword("JSONB_EXISTS");
28265        self.write("(");
28266        self.generate_expression(&e.this)?;
28267        if let Some(path) = &e.path {
28268            self.write(", ");
28269            self.generate_expression(path)?;
28270        }
28271        self.write(")");
28272        Ok(())
28273    }
28274
28275    fn generate_jsonb_extract_scalar(&mut self, e: &JSONBExtractScalar) -> Result<()> {
28276        // JSONB_EXTRACT_SCALAR(this, expression)
28277        self.write_keyword("JSONB_EXTRACT_SCALAR");
28278        self.write("(");
28279        self.generate_expression(&e.this)?;
28280        self.write(", ");
28281        self.generate_expression(&e.expression)?;
28282        self.write(")");
28283        Ok(())
28284    }
28285
28286    fn generate_jsonb_object_agg(&mut self, e: &JSONBObjectAgg) -> Result<()> {
28287        // JSONB_OBJECT_AGG(this, expression)
28288        self.write_keyword("JSONB_OBJECT_AGG");
28289        self.write("(");
28290        self.generate_expression(&e.this)?;
28291        self.write(", ");
28292        self.generate_expression(&e.expression)?;
28293        self.write(")");
28294        Ok(())
28295    }
28296
28297    fn generate_json_column_def(&mut self, e: &JSONColumnDef) -> Result<()> {
28298        // Python: NESTED PATH path schema | this kind PATH path [FOR ORDINALITY]
28299        if let Some(nested_schema) = &e.nested_schema {
28300            self.write_keyword("NESTED");
28301            if let Some(path) = &e.path {
28302                self.write_space();
28303                self.write_keyword("PATH");
28304                self.write_space();
28305                self.generate_expression(path)?;
28306            }
28307            self.write_space();
28308            self.generate_expression(nested_schema)?;
28309        } else {
28310            if let Some(this) = &e.this {
28311                self.generate_expression(this)?;
28312            }
28313            if let Some(kind) = &e.kind {
28314                self.write_space();
28315                self.write(kind);
28316            }
28317            if let Some(path) = &e.path {
28318                self.write_space();
28319                self.write_keyword("PATH");
28320                self.write_space();
28321                self.generate_expression(path)?;
28322            }
28323            if e.ordinality.is_some() {
28324                self.write_keyword(" FOR ORDINALITY");
28325            }
28326        }
28327        Ok(())
28328    }
28329
28330    fn generate_json_exists(&mut self, e: &JSONExists) -> Result<()> {
28331        // JSON_EXISTS(this, path PASSING vars ON ERROR/EMPTY condition)
28332        self.write_keyword("JSON_EXISTS");
28333        self.write("(");
28334        self.generate_expression(&e.this)?;
28335        if let Some(path) = &e.path {
28336            self.write(", ");
28337            self.generate_expression(path)?;
28338        }
28339        if let Some(passing) = &e.passing {
28340            self.write_space();
28341            self.write_keyword("PASSING");
28342            self.write_space();
28343            self.generate_expression(passing)?;
28344        }
28345        if let Some(on_condition) = &e.on_condition {
28346            self.write_space();
28347            self.generate_expression(on_condition)?;
28348        }
28349        self.write(")");
28350        Ok(())
28351    }
28352
28353    fn generate_json_cast(&mut self, e: &JSONCast) -> Result<()> {
28354        self.generate_expression(&e.this)?;
28355        self.write(".:");
28356        self.generate_data_type(&e.to)?;
28357        Ok(())
28358    }
28359
28360    fn generate_json_extract_array(&mut self, e: &JSONExtractArray) -> Result<()> {
28361        // JSON_EXTRACT_ARRAY(this, expression)
28362        self.write_keyword("JSON_EXTRACT_ARRAY");
28363        self.write("(");
28364        self.generate_expression(&e.this)?;
28365        if let Some(expr) = &e.expression {
28366            self.write(", ");
28367            self.generate_expression(expr)?;
28368        }
28369        self.write(")");
28370        Ok(())
28371    }
28372
28373    fn generate_json_extract_quote(&mut self, e: &JSONExtractQuote) -> Result<()> {
28374        // Snowflake: KEEP [OMIT] QUOTES [SCALAR_ONLY] for JSON extraction
28375        if let Some(option) = &e.option {
28376            self.generate_expression(option)?;
28377            self.write_space();
28378        }
28379        self.write_keyword("QUOTES");
28380        if e.scalar.is_some() {
28381            self.write_keyword(" SCALAR_ONLY");
28382        }
28383        Ok(())
28384    }
28385
28386    fn generate_json_extract_scalar(&mut self, e: &JSONExtractScalar) -> Result<()> {
28387        // JSON_EXTRACT_SCALAR(this, expression)
28388        self.write_keyword("JSON_EXTRACT_SCALAR");
28389        self.write("(");
28390        self.generate_expression(&e.this)?;
28391        self.write(", ");
28392        self.generate_expression(&e.expression)?;
28393        self.write(")");
28394        Ok(())
28395    }
28396
28397    fn generate_json_extract_path(&mut self, e: &JSONExtract) -> Result<()> {
28398        // For variant_extract (Snowflake/Databricks colon syntax like a:field)
28399        // Databricks uses col:path syntax, Snowflake uses GET_PATH(col, 'path')
28400        // Otherwise output JSON_EXTRACT(this, expression)
28401        if e.variant_extract.is_some() {
28402            use crate::dialects::DialectType;
28403            if matches!(self.config.dialect, Some(DialectType::Databricks)) {
28404                // Databricks: output col:path syntax (e.g., c1:price, c1:price.foo, c1:price.bar[1])
28405                self.generate_expression(&e.this)?;
28406                self.write(":");
28407                // The expression is a string literal containing the path (e.g., 'price' or 'price.foo')
28408                // We need to output it without quotes
28409                match e.expression.as_ref() {
28410                    Expression::Literal(Literal::String(s)) => {
28411                        self.write(s);
28412                    }
28413                    _ => {
28414                        // Fallback: generate as-is (shouldn't happen in typical cases)
28415                        self.generate_expression(&e.expression)?;
28416                    }
28417                }
28418            } else {
28419                // Snowflake and others: use GET_PATH(col, 'path')
28420                self.write_keyword("GET_PATH");
28421                self.write("(");
28422                self.generate_expression(&e.this)?;
28423                self.write(", ");
28424                self.generate_expression(&e.expression)?;
28425                self.write(")");
28426            }
28427        } else {
28428            self.write_keyword("JSON_EXTRACT");
28429            self.write("(");
28430            self.generate_expression(&e.this)?;
28431            self.write(", ");
28432            self.generate_expression(&e.expression)?;
28433            for expr in &e.expressions {
28434                self.write(", ");
28435                self.generate_expression(expr)?;
28436            }
28437            self.write(")");
28438        }
28439        Ok(())
28440    }
28441
28442    fn generate_json_format(&mut self, e: &JSONFormat) -> Result<()> {
28443        // Output: {expr} FORMAT JSON
28444        // This wraps an expression with FORMAT JSON suffix (Oracle JSON function syntax)
28445        if let Some(this) = &e.this {
28446            self.generate_expression(this)?;
28447            self.write_space();
28448        }
28449        self.write_keyword("FORMAT JSON");
28450        Ok(())
28451    }
28452
28453    fn generate_json_key_value(&mut self, e: &JSONKeyValue) -> Result<()> {
28454        // key: value (for JSON objects)
28455        self.generate_expression(&e.this)?;
28456        self.write(": ");
28457        self.generate_expression(&e.expression)?;
28458        Ok(())
28459    }
28460
28461    fn generate_json_keys(&mut self, e: &JSONKeys) -> Result<()> {
28462        // JSON_KEYS(this, expression, expressions...)
28463        self.write_keyword("JSON_KEYS");
28464        self.write("(");
28465        self.generate_expression(&e.this)?;
28466        if let Some(expr) = &e.expression {
28467            self.write(", ");
28468            self.generate_expression(expr)?;
28469        }
28470        for expr in &e.expressions {
28471            self.write(", ");
28472            self.generate_expression(expr)?;
28473        }
28474        self.write(")");
28475        Ok(())
28476    }
28477
28478    fn generate_json_keys_at_depth(&mut self, e: &JSONKeysAtDepth) -> Result<()> {
28479        // JSON_KEYS(this, expression)
28480        self.write_keyword("JSON_KEYS");
28481        self.write("(");
28482        self.generate_expression(&e.this)?;
28483        if let Some(expr) = &e.expression {
28484            self.write(", ");
28485            self.generate_expression(expr)?;
28486        }
28487        self.write(")");
28488        Ok(())
28489    }
28490
28491    fn generate_json_path_expr(&mut self, e: &JSONPath) -> Result<()> {
28492        // JSONPath expression: generates a quoted path like '$.foo' or '$[0]'
28493        // The path components are concatenated without spaces
28494        let mut path_str = String::new();
28495        for expr in &e.expressions {
28496            match expr {
28497                Expression::JSONPathRoot(_) => {
28498                    path_str.push('$');
28499                }
28500                Expression::JSONPathKey(k) => {
28501                    // .key or ."key" (quote if key has special characters)
28502                    if let Expression::Literal(crate::expressions::Literal::String(s)) =
28503                        k.this.as_ref()
28504                    {
28505                        path_str.push('.');
28506                        // Quote the key if it contains non-alphanumeric characters (hyphens, spaces, etc.)
28507                        let needs_quoting = s.chars().any(|c| !c.is_alphanumeric() && c != '_');
28508                        if needs_quoting {
28509                            path_str.push('"');
28510                            path_str.push_str(s);
28511                            path_str.push('"');
28512                        } else {
28513                            path_str.push_str(s);
28514                        }
28515                    }
28516                }
28517                Expression::JSONPathSubscript(s) => {
28518                    // [index]
28519                    if let Expression::Literal(crate::expressions::Literal::Number(n)) =
28520                        s.this.as_ref()
28521                    {
28522                        path_str.push('[');
28523                        path_str.push_str(n);
28524                        path_str.push(']');
28525                    }
28526                }
28527                _ => {
28528                    // For other path parts, try to generate them
28529                    let mut temp_gen = Self::with_config(self.config.clone());
28530                    temp_gen.generate_expression(expr)?;
28531                    path_str.push_str(&temp_gen.output);
28532                }
28533            }
28534        }
28535        // Output as quoted string
28536        self.write("'");
28537        self.write(&path_str);
28538        self.write("'");
28539        Ok(())
28540    }
28541
28542    fn generate_json_path_filter(&mut self, e: &JSONPathFilter) -> Result<()> {
28543        // JSON path filter: ?(predicate)
28544        self.write("?(");
28545        self.generate_expression(&e.this)?;
28546        self.write(")");
28547        Ok(())
28548    }
28549
28550    fn generate_json_path_key(&mut self, e: &JSONPathKey) -> Result<()> {
28551        // JSON path key: .key or ["key"]
28552        self.write(".");
28553        self.generate_expression(&e.this)?;
28554        Ok(())
28555    }
28556
28557    fn generate_json_path_recursive(&mut self, e: &JSONPathRecursive) -> Result<()> {
28558        // JSON path recursive descent: ..
28559        self.write("..");
28560        if let Some(this) = &e.this {
28561            self.generate_expression(this)?;
28562        }
28563        Ok(())
28564    }
28565
28566    fn generate_json_path_root(&mut self) -> Result<()> {
28567        // JSON path root: $
28568        self.write("$");
28569        Ok(())
28570    }
28571
28572    fn generate_json_path_script(&mut self, e: &JSONPathScript) -> Result<()> {
28573        // JSON path script: (expression)
28574        self.write("(");
28575        self.generate_expression(&e.this)?;
28576        self.write(")");
28577        Ok(())
28578    }
28579
28580    fn generate_json_path_selector(&mut self, e: &JSONPathSelector) -> Result<()> {
28581        // JSON path selector: *
28582        self.generate_expression(&e.this)?;
28583        Ok(())
28584    }
28585
28586    fn generate_json_path_slice(&mut self, e: &JSONPathSlice) -> Result<()> {
28587        // JSON path slice: [start:end:step]
28588        self.write("[");
28589        if let Some(start) = &e.start {
28590            self.generate_expression(start)?;
28591        }
28592        self.write(":");
28593        if let Some(end) = &e.end {
28594            self.generate_expression(end)?;
28595        }
28596        if let Some(step) = &e.step {
28597            self.write(":");
28598            self.generate_expression(step)?;
28599        }
28600        self.write("]");
28601        Ok(())
28602    }
28603
28604    fn generate_json_path_subscript(&mut self, e: &JSONPathSubscript) -> Result<()> {
28605        // JSON path subscript: [index] or [*]
28606        self.write("[");
28607        self.generate_expression(&e.this)?;
28608        self.write("]");
28609        Ok(())
28610    }
28611
28612    fn generate_json_path_union(&mut self, e: &JSONPathUnion) -> Result<()> {
28613        // JSON path union: [key1, key2, ...]
28614        self.write("[");
28615        for (i, expr) in e.expressions.iter().enumerate() {
28616            if i > 0 {
28617                self.write(", ");
28618            }
28619            self.generate_expression(expr)?;
28620        }
28621        self.write("]");
28622        Ok(())
28623    }
28624
28625    fn generate_json_remove(&mut self, e: &JSONRemove) -> Result<()> {
28626        // JSON_REMOVE(this, path1, path2, ...)
28627        self.write_keyword("JSON_REMOVE");
28628        self.write("(");
28629        self.generate_expression(&e.this)?;
28630        for expr in &e.expressions {
28631            self.write(", ");
28632            self.generate_expression(expr)?;
28633        }
28634        self.write(")");
28635        Ok(())
28636    }
28637
28638    fn generate_json_schema(&mut self, e: &JSONSchema) -> Result<()> {
28639        // COLUMNS(col1 type, col2 type, ...)
28640        // When pretty printing and content is too wide, format with each column on a separate line
28641        self.write_keyword("COLUMNS");
28642        self.write("(");
28643
28644        if self.config.pretty && !e.expressions.is_empty() {
28645            // First, generate all expressions into strings to check width
28646            let mut expr_strings: Vec<String> = Vec::with_capacity(e.expressions.len());
28647            for expr in &e.expressions {
28648                let mut temp_gen = Generator::with_config(self.config.clone());
28649                temp_gen.generate_expression(expr)?;
28650                expr_strings.push(temp_gen.output);
28651            }
28652
28653            // Check if total width exceeds max_text_width
28654            if self.too_wide(&expr_strings) {
28655                // Pretty print: each column on its own line
28656                self.write_newline();
28657                self.indent_level += 1;
28658                for (i, expr_str) in expr_strings.iter().enumerate() {
28659                    if i > 0 {
28660                        self.write(",");
28661                        self.write_newline();
28662                    }
28663                    self.write_indent();
28664                    self.write(expr_str);
28665                }
28666                self.write_newline();
28667                self.indent_level -= 1;
28668                self.write_indent();
28669            } else {
28670                // Compact: all on one line
28671                for (i, expr_str) in expr_strings.iter().enumerate() {
28672                    if i > 0 {
28673                        self.write(", ");
28674                    }
28675                    self.write(expr_str);
28676                }
28677            }
28678        } else {
28679            // Non-pretty mode: compact format
28680            for (i, expr) in e.expressions.iter().enumerate() {
28681                if i > 0 {
28682                    self.write(", ");
28683                }
28684                self.generate_expression(expr)?;
28685            }
28686        }
28687        self.write(")");
28688        Ok(())
28689    }
28690
28691    fn generate_json_set(&mut self, e: &JSONSet) -> Result<()> {
28692        // JSON_SET(this, path, value, ...)
28693        self.write_keyword("JSON_SET");
28694        self.write("(");
28695        self.generate_expression(&e.this)?;
28696        for expr in &e.expressions {
28697            self.write(", ");
28698            self.generate_expression(expr)?;
28699        }
28700        self.write(")");
28701        Ok(())
28702    }
28703
28704    fn generate_json_strip_nulls(&mut self, e: &JSONStripNulls) -> Result<()> {
28705        // JSON_STRIP_NULLS(this, expression)
28706        self.write_keyword("JSON_STRIP_NULLS");
28707        self.write("(");
28708        self.generate_expression(&e.this)?;
28709        if let Some(expr) = &e.expression {
28710            self.write(", ");
28711            self.generate_expression(expr)?;
28712        }
28713        self.write(")");
28714        Ok(())
28715    }
28716
28717    fn generate_json_table(&mut self, e: &JSONTable) -> Result<()> {
28718        // JSON_TABLE(this, path [error_handling] [empty_handling] schema)
28719        self.write_keyword("JSON_TABLE");
28720        self.write("(");
28721        self.generate_expression(&e.this)?;
28722        if let Some(path) = &e.path {
28723            self.write(", ");
28724            self.generate_expression(path)?;
28725        }
28726        if let Some(error_handling) = &e.error_handling {
28727            self.write_space();
28728            self.generate_expression(error_handling)?;
28729        }
28730        if let Some(empty_handling) = &e.empty_handling {
28731            self.write_space();
28732            self.generate_expression(empty_handling)?;
28733        }
28734        if let Some(schema) = &e.schema {
28735            self.write_space();
28736            self.generate_expression(schema)?;
28737        }
28738        self.write(")");
28739        Ok(())
28740    }
28741
28742    fn generate_json_type(&mut self, e: &JSONType) -> Result<()> {
28743        // JSON_TYPE(this)
28744        self.write_keyword("JSON_TYPE");
28745        self.write("(");
28746        self.generate_expression(&e.this)?;
28747        self.write(")");
28748        Ok(())
28749    }
28750
28751    fn generate_json_value(&mut self, e: &JSONValue) -> Result<()> {
28752        // JSON_VALUE(this, path RETURNING type ON condition)
28753        self.write_keyword("JSON_VALUE");
28754        self.write("(");
28755        self.generate_expression(&e.this)?;
28756        if let Some(path) = &e.path {
28757            self.write(", ");
28758            self.generate_expression(path)?;
28759        }
28760        if let Some(returning) = &e.returning {
28761            self.write_space();
28762            self.write_keyword("RETURNING");
28763            self.write_space();
28764            self.generate_expression(returning)?;
28765        }
28766        if let Some(on_condition) = &e.on_condition {
28767            self.write_space();
28768            self.generate_expression(on_condition)?;
28769        }
28770        self.write(")");
28771        Ok(())
28772    }
28773
28774    fn generate_json_value_array(&mut self, e: &JSONValueArray) -> Result<()> {
28775        // JSON_VALUE_ARRAY(this)
28776        self.write_keyword("JSON_VALUE_ARRAY");
28777        self.write("(");
28778        self.generate_expression(&e.this)?;
28779        self.write(")");
28780        Ok(())
28781    }
28782
28783    fn generate_jarowinkler_similarity(&mut self, e: &JarowinklerSimilarity) -> Result<()> {
28784        // JAROWINKLER_SIMILARITY(str1, str2)
28785        self.write_keyword("JAROWINKLER_SIMILARITY");
28786        self.write("(");
28787        self.generate_expression(&e.this)?;
28788        self.write(", ");
28789        self.generate_expression(&e.expression)?;
28790        self.write(")");
28791        Ok(())
28792    }
28793
28794    fn generate_join_hint(&mut self, e: &JoinHint) -> Result<()> {
28795        // Python: this(expressions)
28796        self.generate_expression(&e.this)?;
28797        self.write("(");
28798        for (i, expr) in e.expressions.iter().enumerate() {
28799            if i > 0 {
28800                self.write(", ");
28801            }
28802            self.generate_expression(expr)?;
28803        }
28804        self.write(")");
28805        Ok(())
28806    }
28807
28808    fn generate_journal_property(&mut self, e: &JournalProperty) -> Result<()> {
28809        // Python: {no}{local}{dual}{before}{after}JOURNAL
28810        if e.no.is_some() {
28811            self.write_keyword("NO ");
28812        }
28813        if let Some(local) = &e.local {
28814            self.generate_expression(local)?;
28815            self.write_space();
28816        }
28817        if e.dual.is_some() {
28818            self.write_keyword("DUAL ");
28819        }
28820        if e.before.is_some() {
28821            self.write_keyword("BEFORE ");
28822        }
28823        if e.after.is_some() {
28824            self.write_keyword("AFTER ");
28825        }
28826        self.write_keyword("JOURNAL");
28827        Ok(())
28828    }
28829
28830    fn generate_language_property(&mut self, e: &LanguageProperty) -> Result<()> {
28831        // LANGUAGE language_name
28832        self.write_keyword("LANGUAGE");
28833        self.write_space();
28834        self.generate_expression(&e.this)?;
28835        Ok(())
28836    }
28837
28838    fn generate_lateral(&mut self, e: &Lateral) -> Result<()> {
28839        // Python: handles LATERAL VIEW (Hive/Spark) and regular LATERAL
28840        if e.view.is_some() {
28841            // LATERAL VIEW [OUTER] expression [alias] [AS columns]
28842            self.write_keyword("LATERAL VIEW");
28843            if e.outer.is_some() {
28844                self.write_space();
28845                self.write_keyword("OUTER");
28846            }
28847            self.write_space();
28848            self.generate_expression(&e.this)?;
28849            if let Some(alias) = &e.alias {
28850                self.write_space();
28851                self.write(alias);
28852            }
28853        } else {
28854            // LATERAL subquery/function [WITH ORDINALITY] [AS alias(columns)]
28855            self.write_keyword("LATERAL");
28856            self.write_space();
28857            self.generate_expression(&e.this)?;
28858            if e.ordinality.is_some() {
28859                self.write_space();
28860                self.write_keyword("WITH ORDINALITY");
28861            }
28862            if let Some(alias) = &e.alias {
28863                self.write_space();
28864                self.write_keyword("AS");
28865                self.write_space();
28866                self.write(alias);
28867                if !e.column_aliases.is_empty() {
28868                    self.write("(");
28869                    for (i, col) in e.column_aliases.iter().enumerate() {
28870                        if i > 0 {
28871                            self.write(", ");
28872                        }
28873                        self.write(col);
28874                    }
28875                    self.write(")");
28876                }
28877            }
28878        }
28879        Ok(())
28880    }
28881
28882    fn generate_like_property(&mut self, e: &LikeProperty) -> Result<()> {
28883        // Python: LIKE this [options]
28884        self.write_keyword("LIKE");
28885        self.write_space();
28886        self.generate_expression(&e.this)?;
28887        for expr in &e.expressions {
28888            self.write_space();
28889            self.generate_expression(expr)?;
28890        }
28891        Ok(())
28892    }
28893
28894    fn generate_limit(&mut self, e: &Limit) -> Result<()> {
28895        self.write_keyword("LIMIT");
28896        self.write_space();
28897        self.write_limit_expr(&e.this)?;
28898        if e.percent {
28899            self.write_space();
28900            self.write_keyword("PERCENT");
28901        }
28902        // Emit any comments that were captured from before the LIMIT keyword
28903        for comment in &e.comments {
28904            self.write(" ");
28905            self.write_formatted_comment(comment);
28906        }
28907        Ok(())
28908    }
28909
28910    fn generate_limit_options(&mut self, e: &LimitOptions) -> Result<()> {
28911        // Python: [PERCENT][ROWS][WITH TIES|ONLY]
28912        if e.percent.is_some() {
28913            self.write_keyword(" PERCENT");
28914        }
28915        if e.rows.is_some() {
28916            self.write_keyword(" ROWS");
28917        }
28918        if e.with_ties.is_some() {
28919            self.write_keyword(" WITH TIES");
28920        } else if e.rows.is_some() {
28921            self.write_keyword(" ONLY");
28922        }
28923        Ok(())
28924    }
28925
28926    fn generate_list(&mut self, e: &List) -> Result<()> {
28927        use crate::dialects::DialectType;
28928        let is_materialize = matches!(self.config.dialect, Some(DialectType::Materialize));
28929
28930        // Check if this is a subquery-based list (LIST(SELECT ...))
28931        if e.expressions.len() == 1 {
28932            if let Expression::Select(_) = &e.expressions[0] {
28933                self.write_keyword("LIST");
28934                self.write("(");
28935                self.generate_expression(&e.expressions[0])?;
28936                self.write(")");
28937                return Ok(());
28938            }
28939        }
28940
28941        // For Materialize, output as LIST[expr, expr, ...]
28942        if is_materialize {
28943            self.write_keyword("LIST");
28944            self.write("[");
28945            for (i, expr) in e.expressions.iter().enumerate() {
28946                if i > 0 {
28947                    self.write(", ");
28948                }
28949                self.generate_expression(expr)?;
28950            }
28951            self.write("]");
28952        } else {
28953            // For other dialects, output as LIST(expr, expr, ...)
28954            self.write_keyword("LIST");
28955            self.write("(");
28956            for (i, expr) in e.expressions.iter().enumerate() {
28957                if i > 0 {
28958                    self.write(", ");
28959                }
28960                self.generate_expression(expr)?;
28961            }
28962            self.write(")");
28963        }
28964        Ok(())
28965    }
28966
28967    fn generate_tomap(&mut self, e: &ToMap) -> Result<()> {
28968        // Check if this is a subquery-based map (MAP(SELECT ...))
28969        if let Expression::Select(_) = &*e.this {
28970            self.write_keyword("MAP");
28971            self.write("(");
28972            self.generate_expression(&e.this)?;
28973            self.write(")");
28974            return Ok(());
28975        }
28976
28977        let is_duckdb = matches!(self.config.dialect, Some(DialectType::DuckDB));
28978
28979        // For Struct-based map: DuckDB uses MAP {'key': value}, Materialize uses MAP['key' => value]
28980        self.write_keyword("MAP");
28981        if is_duckdb {
28982            self.write(" {");
28983        } else {
28984            self.write("[");
28985        }
28986        if let Expression::Struct(s) = &*e.this {
28987            for (i, (_, expr)) in s.fields.iter().enumerate() {
28988                if i > 0 {
28989                    self.write(", ");
28990                }
28991                if let Expression::PropertyEQ(op) = expr {
28992                    self.generate_expression(&op.left)?;
28993                    if is_duckdb {
28994                        self.write(": ");
28995                    } else {
28996                        self.write(" => ");
28997                    }
28998                    self.generate_expression(&op.right)?;
28999                } else {
29000                    self.generate_expression(expr)?;
29001                }
29002            }
29003        }
29004        if is_duckdb {
29005            self.write("}");
29006        } else {
29007            self.write("]");
29008        }
29009        Ok(())
29010    }
29011
29012    fn generate_localtime(&mut self, e: &Localtime) -> Result<()> {
29013        // Python: LOCALTIME or LOCALTIME(precision)
29014        self.write_keyword("LOCALTIME");
29015        if let Some(precision) = &e.this {
29016            self.write("(");
29017            self.generate_expression(precision)?;
29018            self.write(")");
29019        }
29020        Ok(())
29021    }
29022
29023    fn generate_localtimestamp(&mut self, e: &Localtimestamp) -> Result<()> {
29024        // Python: LOCALTIMESTAMP or LOCALTIMESTAMP(precision)
29025        self.write_keyword("LOCALTIMESTAMP");
29026        if let Some(precision) = &e.this {
29027            self.write("(");
29028            self.generate_expression(precision)?;
29029            self.write(")");
29030        }
29031        Ok(())
29032    }
29033
29034    fn generate_location_property(&mut self, e: &LocationProperty) -> Result<()> {
29035        // LOCATION 'path'
29036        self.write_keyword("LOCATION");
29037        self.write_space();
29038        self.generate_expression(&e.this)?;
29039        Ok(())
29040    }
29041
29042    fn generate_lock(&mut self, e: &Lock) -> Result<()> {
29043        // Python: FOR UPDATE|FOR SHARE [OF tables] [NOWAIT|WAIT n]
29044        if e.update.is_some() {
29045            if e.key.is_some() {
29046                self.write_keyword("FOR NO KEY UPDATE");
29047            } else {
29048                self.write_keyword("FOR UPDATE");
29049            }
29050        } else {
29051            if e.key.is_some() {
29052                self.write_keyword("FOR KEY SHARE");
29053            } else {
29054                self.write_keyword("FOR SHARE");
29055            }
29056        }
29057        if !e.expressions.is_empty() {
29058            self.write_keyword(" OF ");
29059            for (i, expr) in e.expressions.iter().enumerate() {
29060                if i > 0 {
29061                    self.write(", ");
29062                }
29063                self.generate_expression(expr)?;
29064            }
29065        }
29066        // Handle wait option following Python sqlglot convention:
29067        // - Boolean(true) -> NOWAIT
29068        // - Boolean(false) -> SKIP LOCKED
29069        // - Literal (number) -> WAIT n
29070        if let Some(wait) = &e.wait {
29071            match wait.as_ref() {
29072                Expression::Boolean(b) => {
29073                    if b.value {
29074                        self.write_keyword(" NOWAIT");
29075                    } else {
29076                        self.write_keyword(" SKIP LOCKED");
29077                    }
29078                }
29079                _ => {
29080                    // It's a literal (number), output WAIT n
29081                    self.write_keyword(" WAIT ");
29082                    self.generate_expression(wait)?;
29083                }
29084            }
29085        }
29086        Ok(())
29087    }
29088
29089    fn generate_lock_property(&mut self, e: &LockProperty) -> Result<()> {
29090        // LOCK property
29091        self.write_keyword("LOCK");
29092        self.write_space();
29093        self.generate_expression(&e.this)?;
29094        Ok(())
29095    }
29096
29097    fn generate_locking_property(&mut self, e: &LockingProperty) -> Result<()> {
29098        // Python: LOCKING kind [this] [for_or_in] lock_type [OVERRIDE]
29099        self.write_keyword("LOCKING");
29100        self.write_space();
29101        self.write(&e.kind);
29102        if let Some(this) = &e.this {
29103            self.write_space();
29104            self.generate_expression(this)?;
29105        }
29106        if let Some(for_or_in) = &e.for_or_in {
29107            self.write_space();
29108            self.generate_expression(for_or_in)?;
29109        }
29110        if let Some(lock_type) = &e.lock_type {
29111            self.write_space();
29112            self.generate_expression(lock_type)?;
29113        }
29114        if e.override_.is_some() {
29115            self.write_keyword(" OVERRIDE");
29116        }
29117        Ok(())
29118    }
29119
29120    fn generate_locking_statement(&mut self, e: &LockingStatement) -> Result<()> {
29121        // this expression
29122        self.generate_expression(&e.this)?;
29123        self.write_space();
29124        self.generate_expression(&e.expression)?;
29125        Ok(())
29126    }
29127
29128    fn generate_log_property(&mut self, e: &LogProperty) -> Result<()> {
29129        // [NO] LOG
29130        if e.no.is_some() {
29131            self.write_keyword("NO ");
29132        }
29133        self.write_keyword("LOG");
29134        Ok(())
29135    }
29136
29137    fn generate_md5_digest(&mut self, e: &MD5Digest) -> Result<()> {
29138        // MD5(this, expressions...)
29139        self.write_keyword("MD5");
29140        self.write("(");
29141        self.generate_expression(&e.this)?;
29142        for expr in &e.expressions {
29143            self.write(", ");
29144            self.generate_expression(expr)?;
29145        }
29146        self.write(")");
29147        Ok(())
29148    }
29149
29150    fn generate_ml_forecast(&mut self, e: &MLForecast) -> Result<()> {
29151        // ML.FORECAST(model, [params])
29152        self.write_keyword("ML.FORECAST");
29153        self.write("(");
29154        self.generate_expression(&e.this)?;
29155        if let Some(expression) = &e.expression {
29156            self.write(", ");
29157            self.generate_expression(expression)?;
29158        }
29159        if let Some(params) = &e.params_struct {
29160            self.write(", ");
29161            self.generate_expression(params)?;
29162        }
29163        self.write(")");
29164        Ok(())
29165    }
29166
29167    fn generate_ml_translate(&mut self, e: &MLTranslate) -> Result<()> {
29168        // ML.TRANSLATE(model, input, [params])
29169        self.write_keyword("ML.TRANSLATE");
29170        self.write("(");
29171        self.generate_expression(&e.this)?;
29172        self.write(", ");
29173        self.generate_expression(&e.expression)?;
29174        if let Some(params) = &e.params_struct {
29175            self.write(", ");
29176            self.generate_expression(params)?;
29177        }
29178        self.write(")");
29179        Ok(())
29180    }
29181
29182    fn generate_make_interval(&mut self, e: &MakeInterval) -> Result<()> {
29183        // MAKE_INTERVAL(years => x, months => y, ...)
29184        self.write_keyword("MAKE_INTERVAL");
29185        self.write("(");
29186        let mut first = true;
29187        if let Some(year) = &e.year {
29188            self.write("years => ");
29189            self.generate_expression(year)?;
29190            first = false;
29191        }
29192        if let Some(month) = &e.month {
29193            if !first {
29194                self.write(", ");
29195            }
29196            self.write("months => ");
29197            self.generate_expression(month)?;
29198            first = false;
29199        }
29200        if let Some(week) = &e.week {
29201            if !first {
29202                self.write(", ");
29203            }
29204            self.write("weeks => ");
29205            self.generate_expression(week)?;
29206            first = false;
29207        }
29208        if let Some(day) = &e.day {
29209            if !first {
29210                self.write(", ");
29211            }
29212            self.write("days => ");
29213            self.generate_expression(day)?;
29214            first = false;
29215        }
29216        if let Some(hour) = &e.hour {
29217            if !first {
29218                self.write(", ");
29219            }
29220            self.write("hours => ");
29221            self.generate_expression(hour)?;
29222            first = false;
29223        }
29224        if let Some(minute) = &e.minute {
29225            if !first {
29226                self.write(", ");
29227            }
29228            self.write("mins => ");
29229            self.generate_expression(minute)?;
29230            first = false;
29231        }
29232        if let Some(second) = &e.second {
29233            if !first {
29234                self.write(", ");
29235            }
29236            self.write("secs => ");
29237            self.generate_expression(second)?;
29238        }
29239        self.write(")");
29240        Ok(())
29241    }
29242
29243    fn generate_manhattan_distance(&mut self, e: &ManhattanDistance) -> Result<()> {
29244        // MANHATTAN_DISTANCE(vector1, vector2)
29245        self.write_keyword("MANHATTAN_DISTANCE");
29246        self.write("(");
29247        self.generate_expression(&e.this)?;
29248        self.write(", ");
29249        self.generate_expression(&e.expression)?;
29250        self.write(")");
29251        Ok(())
29252    }
29253
29254    fn generate_map(&mut self, e: &Map) -> Result<()> {
29255        // MAP(key1, value1, key2, value2, ...)
29256        self.write_keyword("MAP");
29257        self.write("(");
29258        for (i, (key, value)) in e.keys.iter().zip(e.values.iter()).enumerate() {
29259            if i > 0 {
29260                self.write(", ");
29261            }
29262            self.generate_expression(key)?;
29263            self.write(", ");
29264            self.generate_expression(value)?;
29265        }
29266        self.write(")");
29267        Ok(())
29268    }
29269
29270    fn generate_map_cat(&mut self, e: &MapCat) -> Result<()> {
29271        // MAP_CAT(map1, map2)
29272        self.write_keyword("MAP_CAT");
29273        self.write("(");
29274        self.generate_expression(&e.this)?;
29275        self.write(", ");
29276        self.generate_expression(&e.expression)?;
29277        self.write(")");
29278        Ok(())
29279    }
29280
29281    fn generate_map_delete(&mut self, e: &MapDelete) -> Result<()> {
29282        // MAP_DELETE(map, key1, key2, ...)
29283        self.write_keyword("MAP_DELETE");
29284        self.write("(");
29285        self.generate_expression(&e.this)?;
29286        for expr in &e.expressions {
29287            self.write(", ");
29288            self.generate_expression(expr)?;
29289        }
29290        self.write(")");
29291        Ok(())
29292    }
29293
29294    fn generate_map_insert(&mut self, e: &MapInsert) -> Result<()> {
29295        // MAP_INSERT(map, key, value, [update_flag])
29296        self.write_keyword("MAP_INSERT");
29297        self.write("(");
29298        self.generate_expression(&e.this)?;
29299        if let Some(key) = &e.key {
29300            self.write(", ");
29301            self.generate_expression(key)?;
29302        }
29303        if let Some(value) = &e.value {
29304            self.write(", ");
29305            self.generate_expression(value)?;
29306        }
29307        if let Some(update_flag) = &e.update_flag {
29308            self.write(", ");
29309            self.generate_expression(update_flag)?;
29310        }
29311        self.write(")");
29312        Ok(())
29313    }
29314
29315    fn generate_map_pick(&mut self, e: &MapPick) -> Result<()> {
29316        // MAP_PICK(map, key1, key2, ...)
29317        self.write_keyword("MAP_PICK");
29318        self.write("(");
29319        self.generate_expression(&e.this)?;
29320        for expr in &e.expressions {
29321            self.write(", ");
29322            self.generate_expression(expr)?;
29323        }
29324        self.write(")");
29325        Ok(())
29326    }
29327
29328    fn generate_masking_policy_column_constraint(
29329        &mut self,
29330        e: &MaskingPolicyColumnConstraint,
29331    ) -> Result<()> {
29332        // Python: MASKING POLICY name [USING (cols)]
29333        self.write_keyword("MASKING POLICY");
29334        self.write_space();
29335        self.generate_expression(&e.this)?;
29336        if !e.expressions.is_empty() {
29337            self.write_keyword(" USING");
29338            self.write(" (");
29339            for (i, expr) in e.expressions.iter().enumerate() {
29340                if i > 0 {
29341                    self.write(", ");
29342                }
29343                self.generate_expression(expr)?;
29344            }
29345            self.write(")");
29346        }
29347        Ok(())
29348    }
29349
29350    fn generate_match_against(&mut self, e: &MatchAgainst) -> Result<()> {
29351        if matches!(
29352            self.config.dialect,
29353            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
29354        ) {
29355            if e.expressions.len() > 1 {
29356                self.write("(");
29357            }
29358            for (i, expr) in e.expressions.iter().enumerate() {
29359                if i > 0 {
29360                    self.write_keyword(" OR ");
29361                }
29362                self.generate_expression(expr)?;
29363                self.write_space();
29364                self.write("@@");
29365                self.write_space();
29366                self.generate_expression(&e.this)?;
29367            }
29368            if e.expressions.len() > 1 {
29369                self.write(")");
29370            }
29371            return Ok(());
29372        }
29373
29374        // MATCH(columns) AGAINST(expr [modifier])
29375        self.write_keyword("MATCH");
29376        self.write("(");
29377        for (i, expr) in e.expressions.iter().enumerate() {
29378            if i > 0 {
29379                self.write(", ");
29380            }
29381            self.generate_expression(expr)?;
29382        }
29383        self.write(")");
29384        self.write_keyword(" AGAINST");
29385        self.write("(");
29386        self.generate_expression(&e.this)?;
29387        if let Some(modifier) = &e.modifier {
29388            self.write_space();
29389            self.generate_expression(modifier)?;
29390        }
29391        self.write(")");
29392        Ok(())
29393    }
29394
29395    fn generate_match_recognize_measure(&mut self, e: &MatchRecognizeMeasure) -> Result<()> {
29396        // Python: [window_frame] this
29397        if let Some(window_frame) = &e.window_frame {
29398            self.write(&format!("{:?}", window_frame).to_uppercase());
29399            self.write_space();
29400        }
29401        self.generate_expression(&e.this)?;
29402        Ok(())
29403    }
29404
29405    fn generate_materialized_property(&mut self, e: &MaterializedProperty) -> Result<()> {
29406        // MATERIALIZED [this]
29407        self.write_keyword("MATERIALIZED");
29408        if let Some(this) = &e.this {
29409            self.write_space();
29410            self.generate_expression(this)?;
29411        }
29412        Ok(())
29413    }
29414
29415    fn generate_merge(&mut self, e: &Merge) -> Result<()> {
29416        // MERGE INTO target USING source ON condition WHEN ...
29417        // DuckDB variant: MERGE INTO target USING source USING (key_columns) WHEN ...
29418        if let Some(with_) = &e.with_ {
29419            self.generate_expression(with_)?;
29420            self.write_space();
29421        }
29422        self.write_keyword("MERGE INTO");
29423        self.write_space();
29424        self.generate_expression(&e.this)?;
29425
29426        // USING clause - newline before in pretty mode
29427        if self.config.pretty {
29428            self.write_newline();
29429            self.write_indent();
29430        } else {
29431            self.write_space();
29432        }
29433        self.write_keyword("USING");
29434        self.write_space();
29435        self.generate_expression(&e.using)?;
29436
29437        // ON clause - newline before in pretty mode
29438        if let Some(on) = &e.on {
29439            if self.config.pretty {
29440                self.write_newline();
29441                self.write_indent();
29442            } else {
29443                self.write_space();
29444            }
29445            self.write_keyword("ON");
29446            self.write_space();
29447            self.generate_expression(on)?;
29448        }
29449        // DuckDB USING (key_columns) clause
29450        if let Some(using_cond) = &e.using_cond {
29451            self.write_space();
29452            self.write_keyword("USING");
29453            self.write_space();
29454            self.write("(");
29455            // using_cond is a Tuple containing the column identifiers
29456            if let Expression::Tuple(tuple) = using_cond.as_ref() {
29457                for (i, col) in tuple.expressions.iter().enumerate() {
29458                    if i > 0 {
29459                        self.write(", ");
29460                    }
29461                    self.generate_expression(col)?;
29462                }
29463            } else {
29464                self.generate_expression(using_cond)?;
29465            }
29466            self.write(")");
29467        }
29468        // For PostgreSQL dialect, extract target table name/alias to strip from UPDATE SET
29469        let saved_merge_strip = std::mem::take(&mut self.merge_strip_qualifiers);
29470        if matches!(
29471            self.config.dialect,
29472            Some(crate::DialectType::PostgreSQL)
29473                | Some(crate::DialectType::Redshift)
29474                | Some(crate::DialectType::Trino)
29475                | Some(crate::DialectType::Presto)
29476                | Some(crate::DialectType::Athena)
29477        ) {
29478            let mut names = Vec::new();
29479            match e.this.as_ref() {
29480                Expression::Alias(a) => {
29481                    // e.g., "x AS z" -> strip both "x" and "z"
29482                    if let Expression::Table(t) = &a.this {
29483                        names.push(t.name.name.clone());
29484                    } else if let Expression::Identifier(id) = &a.this {
29485                        names.push(id.name.clone());
29486                    }
29487                    names.push(a.alias.name.clone());
29488                }
29489                Expression::Table(t) => {
29490                    names.push(t.name.name.clone());
29491                }
29492                Expression::Identifier(id) => {
29493                    names.push(id.name.clone());
29494                }
29495                _ => {}
29496            }
29497            self.merge_strip_qualifiers = names;
29498        }
29499
29500        // WHEN clauses - newline before each in pretty mode
29501        if let Some(whens) = &e.whens {
29502            if self.config.pretty {
29503                self.write_newline();
29504                self.write_indent();
29505            } else {
29506                self.write_space();
29507            }
29508            self.generate_expression(whens)?;
29509        }
29510
29511        // Restore merge_strip_qualifiers
29512        self.merge_strip_qualifiers = saved_merge_strip;
29513
29514        // OUTPUT/RETURNING clause - newline before in pretty mode
29515        if let Some(returning) = &e.returning {
29516            if self.config.pretty {
29517                self.write_newline();
29518                self.write_indent();
29519            } else {
29520                self.write_space();
29521            }
29522            self.generate_expression(returning)?;
29523        }
29524        Ok(())
29525    }
29526
29527    fn generate_merge_block_ratio_property(&mut self, e: &MergeBlockRatioProperty) -> Result<()> {
29528        // Python: NO MERGEBLOCKRATIO | DEFAULT MERGEBLOCKRATIO | MERGEBLOCKRATIO=this [PERCENT]
29529        if e.no.is_some() {
29530            self.write_keyword("NO MERGEBLOCKRATIO");
29531        } else if e.default.is_some() {
29532            self.write_keyword("DEFAULT MERGEBLOCKRATIO");
29533        } else {
29534            self.write_keyword("MERGEBLOCKRATIO");
29535            self.write("=");
29536            if let Some(this) = &e.this {
29537                self.generate_expression(this)?;
29538            }
29539            if e.percent.is_some() {
29540                self.write_keyword(" PERCENT");
29541            }
29542        }
29543        Ok(())
29544    }
29545
29546    fn generate_merge_tree_ttl(&mut self, e: &MergeTreeTTL) -> Result<()> {
29547        // TTL expressions [WHERE where] [GROUP BY group] [SET aggregates]
29548        self.write_keyword("TTL");
29549        let pretty_clickhouse = self.config.pretty
29550            && matches!(
29551                self.config.dialect,
29552                Some(crate::dialects::DialectType::ClickHouse)
29553            );
29554
29555        if pretty_clickhouse {
29556            self.write_newline();
29557            self.indent_level += 1;
29558            for (i, expr) in e.expressions.iter().enumerate() {
29559                if i > 0 {
29560                    self.write(",");
29561                    self.write_newline();
29562                }
29563                self.write_indent();
29564                self.generate_expression(expr)?;
29565            }
29566            self.indent_level -= 1;
29567        } else {
29568            self.write_space();
29569            for (i, expr) in e.expressions.iter().enumerate() {
29570                if i > 0 {
29571                    self.write(", ");
29572                }
29573                self.generate_expression(expr)?;
29574            }
29575        }
29576
29577        if let Some(where_) = &e.where_ {
29578            if pretty_clickhouse {
29579                self.write_newline();
29580                if let Expression::Where(w) = where_.as_ref() {
29581                    self.write_indent();
29582                    self.write_keyword("WHERE");
29583                    self.write_newline();
29584                    self.indent_level += 1;
29585                    self.write_indent();
29586                    self.generate_expression(&w.this)?;
29587                    self.indent_level -= 1;
29588                } else {
29589                    self.write_indent();
29590                    self.generate_expression(where_)?;
29591                }
29592            } else {
29593                self.write_space();
29594                self.generate_expression(where_)?;
29595            }
29596        }
29597        if let Some(group) = &e.group {
29598            if pretty_clickhouse {
29599                self.write_newline();
29600                if let Expression::Group(g) = group.as_ref() {
29601                    self.write_indent();
29602                    self.write_keyword("GROUP BY");
29603                    self.write_newline();
29604                    self.indent_level += 1;
29605                    for (i, expr) in g.expressions.iter().enumerate() {
29606                        if i > 0 {
29607                            self.write(",");
29608                            self.write_newline();
29609                        }
29610                        self.write_indent();
29611                        self.generate_expression(expr)?;
29612                    }
29613                    self.indent_level -= 1;
29614                } else {
29615                    self.write_indent();
29616                    self.generate_expression(group)?;
29617                }
29618            } else {
29619                self.write_space();
29620                self.generate_expression(group)?;
29621            }
29622        }
29623        if let Some(aggregates) = &e.aggregates {
29624            if pretty_clickhouse {
29625                self.write_newline();
29626                self.write_indent();
29627                self.write_keyword("SET");
29628                self.write_newline();
29629                self.indent_level += 1;
29630                if let Expression::Tuple(t) = aggregates.as_ref() {
29631                    for (i, agg) in t.expressions.iter().enumerate() {
29632                        if i > 0 {
29633                            self.write(",");
29634                            self.write_newline();
29635                        }
29636                        self.write_indent();
29637                        self.generate_expression(agg)?;
29638                    }
29639                } else {
29640                    self.write_indent();
29641                    self.generate_expression(aggregates)?;
29642                }
29643                self.indent_level -= 1;
29644            } else {
29645                self.write_space();
29646                self.write_keyword("SET");
29647                self.write_space();
29648                self.generate_expression(aggregates)?;
29649            }
29650        }
29651        Ok(())
29652    }
29653
29654    fn generate_merge_tree_ttl_action(&mut self, e: &MergeTreeTTLAction) -> Result<()> {
29655        // Python: this [DELETE] [RECOMPRESS codec] [TO DISK disk] [TO VOLUME volume]
29656        self.generate_expression(&e.this)?;
29657        if e.delete.is_some() {
29658            self.write_keyword(" DELETE");
29659        }
29660        if let Some(recompress) = &e.recompress {
29661            self.write_keyword(" RECOMPRESS ");
29662            self.generate_expression(recompress)?;
29663        }
29664        if let Some(to_disk) = &e.to_disk {
29665            self.write_keyword(" TO DISK ");
29666            self.generate_expression(to_disk)?;
29667        }
29668        if let Some(to_volume) = &e.to_volume {
29669            self.write_keyword(" TO VOLUME ");
29670            self.generate_expression(to_volume)?;
29671        }
29672        Ok(())
29673    }
29674
29675    fn generate_minhash(&mut self, e: &Minhash) -> Result<()> {
29676        // MINHASH(this, expressions...)
29677        self.write_keyword("MINHASH");
29678        self.write("(");
29679        self.generate_expression(&e.this)?;
29680        for expr in &e.expressions {
29681            self.write(", ");
29682            self.generate_expression(expr)?;
29683        }
29684        self.write(")");
29685        Ok(())
29686    }
29687
29688    fn generate_model_attribute(&mut self, e: &ModelAttribute) -> Result<()> {
29689        // model!attribute - Snowflake syntax
29690        self.generate_expression(&e.this)?;
29691        self.write("!");
29692        self.generate_expression(&e.expression)?;
29693        Ok(())
29694    }
29695
29696    fn generate_monthname(&mut self, e: &Monthname) -> Result<()> {
29697        // MONTHNAME(this)
29698        self.write_keyword("MONTHNAME");
29699        self.write("(");
29700        self.generate_expression(&e.this)?;
29701        self.write(")");
29702        Ok(())
29703    }
29704
29705    fn generate_multitable_inserts(&mut self, e: &MultitableInserts) -> Result<()> {
29706        // Output leading comments
29707        for comment in &e.leading_comments {
29708            self.write_formatted_comment(comment);
29709            if self.config.pretty {
29710                self.write_newline();
29711                self.write_indent();
29712            } else {
29713                self.write_space();
29714            }
29715        }
29716        // Python: INSERT kind expressions source
29717        self.write_keyword("INSERT");
29718        self.write_space();
29719        self.write(&e.kind);
29720        if self.config.pretty {
29721            self.indent_level += 1;
29722            for expr in &e.expressions {
29723                self.write_newline();
29724                self.write_indent();
29725                self.generate_expression(expr)?;
29726            }
29727            self.indent_level -= 1;
29728        } else {
29729            for expr in &e.expressions {
29730                self.write_space();
29731                self.generate_expression(expr)?;
29732            }
29733        }
29734        if let Some(source) = &e.source {
29735            if self.config.pretty {
29736                self.write_newline();
29737                self.write_indent();
29738            } else {
29739                self.write_space();
29740            }
29741            self.generate_expression(source)?;
29742        }
29743        Ok(())
29744    }
29745
29746    fn generate_next_value_for(&mut self, e: &NextValueFor) -> Result<()> {
29747        // Python: NEXT VALUE FOR this [OVER (order)]
29748        self.write_keyword("NEXT VALUE FOR");
29749        self.write_space();
29750        self.generate_expression(&e.this)?;
29751        if let Some(order) = &e.order {
29752            self.write_space();
29753            self.write_keyword("OVER");
29754            self.write(" (");
29755            self.generate_expression(order)?;
29756            self.write(")");
29757        }
29758        Ok(())
29759    }
29760
29761    fn generate_normal(&mut self, e: &Normal) -> Result<()> {
29762        // NORMAL(mean, stddev, gen)
29763        self.write_keyword("NORMAL");
29764        self.write("(");
29765        self.generate_expression(&e.this)?;
29766        if let Some(stddev) = &e.stddev {
29767            self.write(", ");
29768            self.generate_expression(stddev)?;
29769        }
29770        if let Some(gen) = &e.gen {
29771            self.write(", ");
29772            self.generate_expression(gen)?;
29773        }
29774        self.write(")");
29775        Ok(())
29776    }
29777
29778    fn generate_normalize(&mut self, e: &Normalize) -> Result<()> {
29779        // NORMALIZE(this, form) or CASEFOLD version
29780        if e.is_casefold.is_some() {
29781            self.write_keyword("NORMALIZE_AND_CASEFOLD");
29782        } else {
29783            self.write_keyword("NORMALIZE");
29784        }
29785        self.write("(");
29786        self.generate_expression(&e.this)?;
29787        if let Some(form) = &e.form {
29788            self.write(", ");
29789            self.generate_expression(form)?;
29790        }
29791        self.write(")");
29792        Ok(())
29793    }
29794
29795    fn generate_not_null_column_constraint(&mut self, e: &NotNullColumnConstraint) -> Result<()> {
29796        // Python: [NOT ]NULL
29797        if e.allow_null.is_none() {
29798            self.write_keyword("NOT ");
29799        }
29800        self.write_keyword("NULL");
29801        Ok(())
29802    }
29803
29804    fn generate_nullif(&mut self, e: &Nullif) -> Result<()> {
29805        // NULLIF(this, expression)
29806        self.write_keyword("NULLIF");
29807        self.write("(");
29808        self.generate_expression(&e.this)?;
29809        self.write(", ");
29810        self.generate_expression(&e.expression)?;
29811        self.write(")");
29812        Ok(())
29813    }
29814
29815    fn generate_number_to_str(&mut self, e: &NumberToStr) -> Result<()> {
29816        // FORMAT(this, format, culture)
29817        self.write_keyword("FORMAT");
29818        self.write("(");
29819        self.generate_expression(&e.this)?;
29820        self.write(", '");
29821        self.write(&e.format);
29822        self.write("'");
29823        if let Some(culture) = &e.culture {
29824            self.write(", ");
29825            self.generate_expression(culture)?;
29826        }
29827        self.write(")");
29828        Ok(())
29829    }
29830
29831    fn generate_object_agg(&mut self, e: &ObjectAgg) -> Result<()> {
29832        // OBJECT_AGG(key, value)
29833        self.write_keyword("OBJECT_AGG");
29834        self.write("(");
29835        self.generate_expression(&e.this)?;
29836        self.write(", ");
29837        self.generate_expression(&e.expression)?;
29838        self.write(")");
29839        Ok(())
29840    }
29841
29842    fn generate_object_identifier(&mut self, e: &ObjectIdentifier) -> Result<()> {
29843        // Python: Just returns the name
29844        self.generate_expression(&e.this)?;
29845        Ok(())
29846    }
29847
29848    fn generate_object_insert(&mut self, e: &ObjectInsert) -> Result<()> {
29849        // OBJECT_INSERT(obj, key, value, [update_flag])
29850        self.write_keyword("OBJECT_INSERT");
29851        self.write("(");
29852        self.generate_expression(&e.this)?;
29853        if let Some(key) = &e.key {
29854            self.write(", ");
29855            self.generate_expression(key)?;
29856        }
29857        if let Some(value) = &e.value {
29858            self.write(", ");
29859            self.generate_expression(value)?;
29860        }
29861        if let Some(update_flag) = &e.update_flag {
29862            self.write(", ");
29863            self.generate_expression(update_flag)?;
29864        }
29865        self.write(")");
29866        Ok(())
29867    }
29868
29869    fn generate_offset(&mut self, e: &Offset) -> Result<()> {
29870        // OFFSET value [ROW|ROWS]
29871        self.write_keyword("OFFSET");
29872        self.write_space();
29873        self.generate_expression(&e.this)?;
29874        // Output ROWS keyword only for TSQL/Oracle targets
29875        if e.rows == Some(true)
29876            && matches!(
29877                self.config.dialect,
29878                Some(crate::dialects::DialectType::TSQL)
29879                    | Some(crate::dialects::DialectType::Oracle)
29880            )
29881        {
29882            self.write_space();
29883            self.write_keyword("ROWS");
29884        }
29885        Ok(())
29886    }
29887
29888    fn generate_qualify(&mut self, e: &Qualify) -> Result<()> {
29889        // QUALIFY condition (Snowflake/BigQuery)
29890        self.write_keyword("QUALIFY");
29891        self.write_space();
29892        self.generate_expression(&e.this)?;
29893        Ok(())
29894    }
29895
29896    fn generate_on_cluster(&mut self, e: &OnCluster) -> Result<()> {
29897        // ON CLUSTER cluster_name
29898        self.write_keyword("ON CLUSTER");
29899        self.write_space();
29900        self.generate_expression(&e.this)?;
29901        Ok(())
29902    }
29903
29904    fn generate_on_commit_property(&mut self, e: &OnCommitProperty) -> Result<()> {
29905        // ON COMMIT [DELETE ROWS | PRESERVE ROWS]
29906        self.write_keyword("ON COMMIT");
29907        if e.delete.is_some() {
29908            self.write_keyword(" DELETE ROWS");
29909        } else {
29910            self.write_keyword(" PRESERVE ROWS");
29911        }
29912        Ok(())
29913    }
29914
29915    fn generate_on_condition(&mut self, e: &OnCondition) -> Result<()> {
29916        // Python: error/empty/null handling
29917        if let Some(empty) = &e.empty {
29918            self.generate_expression(empty)?;
29919            self.write_keyword(" ON EMPTY");
29920        }
29921        if let Some(error) = &e.error {
29922            if e.empty.is_some() {
29923                self.write_space();
29924            }
29925            self.generate_expression(error)?;
29926            self.write_keyword(" ON ERROR");
29927        }
29928        if let Some(null) = &e.null {
29929            if e.empty.is_some() || e.error.is_some() {
29930                self.write_space();
29931            }
29932            self.generate_expression(null)?;
29933            self.write_keyword(" ON NULL");
29934        }
29935        Ok(())
29936    }
29937
29938    fn generate_on_conflict(&mut self, e: &OnConflict) -> Result<()> {
29939        // Materialize doesn't support ON CONFLICT - skip entirely
29940        if matches!(self.config.dialect, Some(DialectType::Materialize)) {
29941            return Ok(());
29942        }
29943        // Python: ON CONFLICT|ON DUPLICATE KEY [ON CONSTRAINT constraint] [conflict_keys] action
29944        if e.duplicate.is_some() {
29945            // MySQL: ON DUPLICATE KEY UPDATE col = val, ...
29946            self.write_keyword("ON DUPLICATE KEY UPDATE");
29947            for (i, expr) in e.expressions.iter().enumerate() {
29948                if i > 0 {
29949                    self.write(",");
29950                }
29951                self.write_space();
29952                self.generate_expression(expr)?;
29953            }
29954            return Ok(());
29955        } else {
29956            self.write_keyword("ON CONFLICT");
29957        }
29958        if let Some(constraint) = &e.constraint {
29959            self.write_keyword(" ON CONSTRAINT ");
29960            self.generate_expression(constraint)?;
29961        }
29962        if let Some(conflict_keys) = &e.conflict_keys {
29963            // conflict_keys can be a Tuple containing expressions
29964            if let Expression::Tuple(t) = conflict_keys.as_ref() {
29965                self.write("(");
29966                for (i, expr) in t.expressions.iter().enumerate() {
29967                    if i > 0 {
29968                        self.write(", ");
29969                    }
29970                    self.generate_expression(expr)?;
29971                }
29972                self.write(")");
29973            } else {
29974                self.write("(");
29975                self.generate_expression(conflict_keys)?;
29976                self.write(")");
29977            }
29978        }
29979        if let Some(index_predicate) = &e.index_predicate {
29980            self.write_keyword(" WHERE ");
29981            self.generate_expression(index_predicate)?;
29982        }
29983        if let Some(action) = &e.action {
29984            // Check if action is "NOTHING" or an UPDATE set
29985            if let Expression::Identifier(id) = action.as_ref() {
29986                if id.name == "NOTHING" || id.name.to_uppercase() == "NOTHING" {
29987                    self.write_keyword(" DO NOTHING");
29988                } else {
29989                    self.write_keyword(" DO ");
29990                    self.generate_expression(action)?;
29991                }
29992            } else if let Expression::Tuple(t) = action.as_ref() {
29993                // DO UPDATE SET col1 = val1, col2 = val2
29994                self.write_keyword(" DO UPDATE SET ");
29995                for (i, expr) in t.expressions.iter().enumerate() {
29996                    if i > 0 {
29997                        self.write(", ");
29998                    }
29999                    self.generate_expression(expr)?;
30000                }
30001            } else {
30002                self.write_keyword(" DO ");
30003                self.generate_expression(action)?;
30004            }
30005        }
30006        // WHERE clause for the UPDATE action
30007        if let Some(where_) = &e.where_ {
30008            self.write_keyword(" WHERE ");
30009            self.generate_expression(where_)?;
30010        }
30011        Ok(())
30012    }
30013
30014    fn generate_on_property(&mut self, e: &OnProperty) -> Result<()> {
30015        // ON property_value
30016        self.write_keyword("ON");
30017        self.write_space();
30018        self.generate_expression(&e.this)?;
30019        Ok(())
30020    }
30021
30022    fn generate_opclass(&mut self, e: &Opclass) -> Result<()> {
30023        // Python: this expression (e.g., column opclass)
30024        self.generate_expression(&e.this)?;
30025        self.write_space();
30026        self.generate_expression(&e.expression)?;
30027        Ok(())
30028    }
30029
30030    fn generate_open_json(&mut self, e: &OpenJSON) -> Result<()> {
30031        // Python: OPENJSON(this[, path]) [WITH (columns)]
30032        self.write_keyword("OPENJSON");
30033        self.write("(");
30034        self.generate_expression(&e.this)?;
30035        if let Some(path) = &e.path {
30036            self.write(", ");
30037            self.generate_expression(path)?;
30038        }
30039        self.write(")");
30040        if !e.expressions.is_empty() {
30041            self.write_keyword(" WITH");
30042            if self.config.pretty {
30043                self.write(" (\n");
30044                self.indent_level += 2;
30045                for (i, expr) in e.expressions.iter().enumerate() {
30046                    if i > 0 {
30047                        self.write(",\n");
30048                    }
30049                    self.write_indent();
30050                    self.generate_expression(expr)?;
30051                }
30052                self.write("\n");
30053                self.indent_level -= 2;
30054                self.write(")");
30055            } else {
30056                self.write(" (");
30057                for (i, expr) in e.expressions.iter().enumerate() {
30058                    if i > 0 {
30059                        self.write(", ");
30060                    }
30061                    self.generate_expression(expr)?;
30062                }
30063                self.write(")");
30064            }
30065        }
30066        Ok(())
30067    }
30068
30069    fn generate_open_json_column_def(&mut self, e: &OpenJSONColumnDef) -> Result<()> {
30070        // Python: this kind [path] [AS JSON]
30071        self.generate_expression(&e.this)?;
30072        self.write_space();
30073        // Use parsed data_type if available, otherwise fall back to kind string
30074        if let Some(ref dt) = e.data_type {
30075            self.generate_data_type(dt)?;
30076        } else if !e.kind.is_empty() {
30077            self.write(&e.kind);
30078        }
30079        if let Some(path) = &e.path {
30080            self.write_space();
30081            self.generate_expression(path)?;
30082        }
30083        if e.as_json.is_some() {
30084            self.write_keyword(" AS JSON");
30085        }
30086        Ok(())
30087    }
30088
30089    fn generate_operator(&mut self, e: &Operator) -> Result<()> {
30090        // this OPERATOR(op) expression
30091        self.generate_expression(&e.this)?;
30092        self.write_space();
30093        if let Some(op) = &e.operator {
30094            self.write_keyword("OPERATOR");
30095            self.write("(");
30096            self.generate_expression(op)?;
30097            self.write(")");
30098        }
30099        // Emit inline comments between OPERATOR() and the RHS
30100        for comment in &e.comments {
30101            self.write_space();
30102            self.write_formatted_comment(comment);
30103        }
30104        self.write_space();
30105        self.generate_expression(&e.expression)?;
30106        Ok(())
30107    }
30108
30109    fn generate_order_by(&mut self, e: &OrderBy) -> Result<()> {
30110        // ORDER BY expr1 [ASC|DESC] [NULLS FIRST|LAST], expr2 ...
30111        self.write_keyword("ORDER BY");
30112        let pretty_clickhouse_single_paren = self.config.pretty
30113            && matches!(self.config.dialect, Some(DialectType::ClickHouse))
30114            && e.expressions.len() == 1
30115            && matches!(e.expressions[0].this, Expression::Paren(ref p) if !matches!(p.this, Expression::Tuple(_)));
30116        let clickhouse_single_tuple = matches!(self.config.dialect, Some(DialectType::ClickHouse))
30117            && e.expressions.len() == 1
30118            && matches!(e.expressions[0].this, Expression::Tuple(_))
30119            && !e.expressions[0].desc
30120            && e.expressions[0].nulls_first.is_none();
30121
30122        if pretty_clickhouse_single_paren {
30123            self.write_space();
30124            if let Expression::Paren(p) = &e.expressions[0].this {
30125                self.write("(");
30126                self.write_newline();
30127                self.indent_level += 1;
30128                self.write_indent();
30129                self.generate_expression(&p.this)?;
30130                self.indent_level -= 1;
30131                self.write_newline();
30132                self.write(")");
30133            }
30134            return Ok(());
30135        }
30136
30137        if clickhouse_single_tuple {
30138            self.write_space();
30139            if let Expression::Tuple(t) = &e.expressions[0].this {
30140                self.write("(");
30141                for (i, expr) in t.expressions.iter().enumerate() {
30142                    if i > 0 {
30143                        self.write(", ");
30144                    }
30145                    self.generate_expression(expr)?;
30146                }
30147                self.write(")");
30148            }
30149            return Ok(());
30150        }
30151
30152        self.write_space();
30153        for (i, ordered) in e.expressions.iter().enumerate() {
30154            if i > 0 {
30155                self.write(", ");
30156            }
30157            self.generate_expression(&ordered.this)?;
30158            if ordered.desc {
30159                self.write_space();
30160                self.write_keyword("DESC");
30161            } else if ordered.explicit_asc {
30162                self.write_space();
30163                self.write_keyword("ASC");
30164            }
30165            if let Some(nulls_first) = ordered.nulls_first {
30166                // In Dremio, NULLS LAST is the default, so skip generating it
30167                let skip_nulls_last =
30168                    !nulls_first && matches!(self.config.dialect, Some(DialectType::Dremio));
30169                if !skip_nulls_last {
30170                    self.write_space();
30171                    self.write_keyword("NULLS");
30172                    self.write_space();
30173                    if nulls_first {
30174                        self.write_keyword("FIRST");
30175                    } else {
30176                        self.write_keyword("LAST");
30177                    }
30178                }
30179            }
30180        }
30181        Ok(())
30182    }
30183
30184    fn generate_output_model_property(&mut self, e: &OutputModelProperty) -> Result<()> {
30185        // OUTPUT(model)
30186        self.write_keyword("OUTPUT");
30187        self.write("(");
30188        if self.config.pretty {
30189            self.indent_level += 1;
30190            self.write_newline();
30191            self.write_indent();
30192            self.generate_expression(&e.this)?;
30193            self.indent_level -= 1;
30194            self.write_newline();
30195        } else {
30196            self.generate_expression(&e.this)?;
30197        }
30198        self.write(")");
30199        Ok(())
30200    }
30201
30202    fn generate_overflow_truncate_behavior(&mut self, e: &OverflowTruncateBehavior) -> Result<()> {
30203        // Python: TRUNCATE [filler] WITH|WITHOUT COUNT
30204        self.write_keyword("TRUNCATE");
30205        if let Some(this) = &e.this {
30206            self.write_space();
30207            self.generate_expression(this)?;
30208        }
30209        if e.with_count.is_some() {
30210            self.write_keyword(" WITH COUNT");
30211        } else {
30212            self.write_keyword(" WITHOUT COUNT");
30213        }
30214        Ok(())
30215    }
30216
30217    fn generate_parameterized_agg(&mut self, e: &ParameterizedAgg) -> Result<()> {
30218        // Python: name(expressions)(params)
30219        self.generate_expression(&e.this)?;
30220        self.write("(");
30221        for (i, expr) in e.expressions.iter().enumerate() {
30222            if i > 0 {
30223                self.write(", ");
30224            }
30225            self.generate_expression(expr)?;
30226        }
30227        self.write(")(");
30228        for (i, param) in e.params.iter().enumerate() {
30229            if i > 0 {
30230                self.write(", ");
30231            }
30232            self.generate_expression(param)?;
30233        }
30234        self.write(")");
30235        Ok(())
30236    }
30237
30238    fn generate_parse_datetime(&mut self, e: &ParseDatetime) -> Result<()> {
30239        // PARSE_DATETIME(format, this) or similar
30240        self.write_keyword("PARSE_DATETIME");
30241        self.write("(");
30242        if let Some(format) = &e.format {
30243            self.write("'");
30244            self.write(format);
30245            self.write("', ");
30246        }
30247        self.generate_expression(&e.this)?;
30248        if let Some(zone) = &e.zone {
30249            self.write(", ");
30250            self.generate_expression(zone)?;
30251        }
30252        self.write(")");
30253        Ok(())
30254    }
30255
30256    fn generate_parse_ip(&mut self, e: &ParseIp) -> Result<()> {
30257        // PARSE_IP(this, type, permissive)
30258        self.write_keyword("PARSE_IP");
30259        self.write("(");
30260        self.generate_expression(&e.this)?;
30261        if let Some(type_) = &e.type_ {
30262            self.write(", ");
30263            self.generate_expression(type_)?;
30264        }
30265        if let Some(permissive) = &e.permissive {
30266            self.write(", ");
30267            self.generate_expression(permissive)?;
30268        }
30269        self.write(")");
30270        Ok(())
30271    }
30272
30273    fn generate_parse_json(&mut self, e: &ParseJSON) -> Result<()> {
30274        // PARSE_JSON(this, [expression])
30275        self.write_keyword("PARSE_JSON");
30276        self.write("(");
30277        self.generate_expression(&e.this)?;
30278        if let Some(expression) = &e.expression {
30279            self.write(", ");
30280            self.generate_expression(expression)?;
30281        }
30282        self.write(")");
30283        Ok(())
30284    }
30285
30286    fn generate_parse_time(&mut self, e: &ParseTime) -> Result<()> {
30287        // PARSE_TIME(format, this) or STR_TO_TIME(this, format)
30288        self.write_keyword("PARSE_TIME");
30289        self.write("(");
30290        self.write(&format!("'{}'", e.format));
30291        self.write(", ");
30292        self.generate_expression(&e.this)?;
30293        self.write(")");
30294        Ok(())
30295    }
30296
30297    fn generate_parse_url(&mut self, e: &ParseUrl) -> Result<()> {
30298        // PARSE_URL(this, [part_to_extract], [key], [permissive])
30299        self.write_keyword("PARSE_URL");
30300        self.write("(");
30301        self.generate_expression(&e.this)?;
30302        if let Some(part) = &e.part_to_extract {
30303            self.write(", ");
30304            self.generate_expression(part)?;
30305        }
30306        if let Some(key) = &e.key {
30307            self.write(", ");
30308            self.generate_expression(key)?;
30309        }
30310        if let Some(permissive) = &e.permissive {
30311            self.write(", ");
30312            self.generate_expression(permissive)?;
30313        }
30314        self.write(")");
30315        Ok(())
30316    }
30317
30318    fn generate_partition_expr(&mut self, e: &Partition) -> Result<()> {
30319        // PARTITION(expr1, expr2, ...) or SUBPARTITION(expr1, expr2, ...)
30320        if e.subpartition {
30321            self.write_keyword("SUBPARTITION");
30322        } else {
30323            self.write_keyword("PARTITION");
30324        }
30325        self.write("(");
30326        for (i, expr) in e.expressions.iter().enumerate() {
30327            if i > 0 {
30328                self.write(", ");
30329            }
30330            self.generate_expression(expr)?;
30331        }
30332        self.write(")");
30333        Ok(())
30334    }
30335
30336    fn generate_partition_bound_spec(&mut self, e: &PartitionBoundSpec) -> Result<()> {
30337        // IN (values) or WITH (MODULUS this, REMAINDER expression) or FROM (from) TO (to)
30338        if let Some(this) = &e.this {
30339            if let Some(expression) = &e.expression {
30340                // WITH (MODULUS this, REMAINDER expression)
30341                self.write_keyword("WITH");
30342                self.write(" (");
30343                self.write_keyword("MODULUS");
30344                self.write_space();
30345                self.generate_expression(this)?;
30346                self.write(", ");
30347                self.write_keyword("REMAINDER");
30348                self.write_space();
30349                self.generate_expression(expression)?;
30350                self.write(")");
30351            } else {
30352                // IN (this) - this could be a list
30353                self.write_keyword("IN");
30354                self.write(" (");
30355                self.generate_partition_bound_values(this)?;
30356                self.write(")");
30357            }
30358        } else if let (Some(from), Some(to)) = (&e.from_expressions, &e.to_expressions) {
30359            // FROM (from_expressions) TO (to_expressions)
30360            self.write_keyword("FROM");
30361            self.write(" (");
30362            self.generate_partition_bound_values(from)?;
30363            self.write(") ");
30364            self.write_keyword("TO");
30365            self.write(" (");
30366            self.generate_partition_bound_values(to)?;
30367            self.write(")");
30368        }
30369        Ok(())
30370    }
30371
30372    /// Generate partition bound values - handles Tuple expressions by outputting
30373    /// contents without wrapping parens (since caller provides the parens)
30374    fn generate_partition_bound_values(&mut self, expr: &Expression) -> Result<()> {
30375        if let Expression::Tuple(t) = expr {
30376            for (i, e) in t.expressions.iter().enumerate() {
30377                if i > 0 {
30378                    self.write(", ");
30379                }
30380                self.generate_expression(e)?;
30381            }
30382            Ok(())
30383        } else {
30384            self.generate_expression(expr)
30385        }
30386    }
30387
30388    fn generate_partition_by_list_property(&mut self, e: &PartitionByListProperty) -> Result<()> {
30389        // PARTITION BY LIST (partition_expressions) (create_expressions)
30390        self.write_keyword("PARTITION BY LIST");
30391        if let Some(partition_exprs) = &e.partition_expressions {
30392            self.write(" (");
30393            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
30394            self.generate_doris_partition_expressions(partition_exprs)?;
30395            self.write(")");
30396        }
30397        if let Some(create_exprs) = &e.create_expressions {
30398            self.write(" (");
30399            // Unwrap Tuple for partition definitions
30400            self.generate_doris_partition_definitions(create_exprs)?;
30401            self.write(")");
30402        }
30403        Ok(())
30404    }
30405
30406    fn generate_partition_by_range_property(&mut self, e: &PartitionByRangeProperty) -> Result<()> {
30407        // PARTITION BY RANGE (partition_expressions) (create_expressions)
30408        self.write_keyword("PARTITION BY RANGE");
30409        if let Some(partition_exprs) = &e.partition_expressions {
30410            self.write(" (");
30411            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
30412            self.generate_doris_partition_expressions(partition_exprs)?;
30413            self.write(")");
30414        }
30415        if let Some(create_exprs) = &e.create_expressions {
30416            self.write(" (");
30417            // Check for dynamic partition (PartitionByRangePropertyDynamic) or static (Tuple of Partition)
30418            self.generate_doris_partition_definitions(create_exprs)?;
30419            self.write(")");
30420        }
30421        Ok(())
30422    }
30423
30424    /// Generate Doris partition column expressions (unwrap Tuple)
30425    fn generate_doris_partition_expressions(&mut self, expr: &Expression) -> Result<()> {
30426        if let Expression::Tuple(t) = expr {
30427            for (i, e) in t.expressions.iter().enumerate() {
30428                if i > 0 {
30429                    self.write(", ");
30430                }
30431                self.generate_expression(e)?;
30432            }
30433        } else {
30434            self.generate_expression(expr)?;
30435        }
30436        Ok(())
30437    }
30438
30439    /// Generate Doris partition definitions (comma-separated Partition expressions)
30440    fn generate_doris_partition_definitions(&mut self, expr: &Expression) -> Result<()> {
30441        match expr {
30442            Expression::Tuple(t) => {
30443                // Multiple partitions, comma-separated
30444                for (i, part) in t.expressions.iter().enumerate() {
30445                    if i > 0 {
30446                        self.write(", ");
30447                    }
30448                    // For Partition expressions, generate the inner PartitionRange/PartitionList directly
30449                    if let Expression::Partition(p) = part {
30450                        for (j, inner) in p.expressions.iter().enumerate() {
30451                            if j > 0 {
30452                                self.write(", ");
30453                            }
30454                            self.generate_expression(inner)?;
30455                        }
30456                    } else {
30457                        self.generate_expression(part)?;
30458                    }
30459                }
30460            }
30461            Expression::PartitionByRangePropertyDynamic(_) => {
30462                // Dynamic partition - FROM/TO/INTERVAL
30463                self.generate_expression(expr)?;
30464            }
30465            _ => {
30466                self.generate_expression(expr)?;
30467            }
30468        }
30469        Ok(())
30470    }
30471
30472    fn generate_partition_by_range_property_dynamic(
30473        &mut self,
30474        e: &PartitionByRangePropertyDynamic,
30475    ) -> Result<()> {
30476        if e.use_start_end {
30477            // StarRocks: START ('val') END ('val') EVERY (expr)
30478            if let Some(start) = &e.start {
30479                self.write_keyword("START");
30480                self.write(" (");
30481                self.generate_expression(start)?;
30482                self.write(")");
30483            }
30484            if let Some(end) = &e.end {
30485                self.write_space();
30486                self.write_keyword("END");
30487                self.write(" (");
30488                self.generate_expression(end)?;
30489                self.write(")");
30490            }
30491            if let Some(every) = &e.every {
30492                self.write_space();
30493                self.write_keyword("EVERY");
30494                self.write(" (");
30495                // Use unquoted interval format for StarRocks
30496                self.generate_doris_interval(every)?;
30497                self.write(")");
30498            }
30499        } else {
30500            // Doris: FROM (start) TO (end) INTERVAL n UNIT
30501            if let Some(start) = &e.start {
30502                self.write_keyword("FROM");
30503                self.write(" (");
30504                self.generate_expression(start)?;
30505                self.write(")");
30506            }
30507            if let Some(end) = &e.end {
30508                self.write_space();
30509                self.write_keyword("TO");
30510                self.write(" (");
30511                self.generate_expression(end)?;
30512                self.write(")");
30513            }
30514            if let Some(every) = &e.every {
30515                self.write_space();
30516                // Generate INTERVAL n UNIT (not quoted, for Doris dynamic partition)
30517                self.generate_doris_interval(every)?;
30518            }
30519        }
30520        Ok(())
30521    }
30522
30523    /// Generate Doris-style interval without quoting numbers: INTERVAL n UNIT
30524    fn generate_doris_interval(&mut self, expr: &Expression) -> Result<()> {
30525        if let Expression::Interval(interval) = expr {
30526            self.write_keyword("INTERVAL");
30527            if let Some(ref value) = interval.this {
30528                self.write_space();
30529                // If the value is a string literal that looks like a number,
30530                // output it without quotes (matching Python sqlglot's
30531                // partitionbyrangepropertydynamic_sql which converts back to number)
30532                match value {
30533                    Expression::Literal(Literal::String(s))
30534                        if s.chars()
30535                            .all(|c| c.is_ascii_digit() || c == '.' || c == '-')
30536                            && !s.is_empty() =>
30537                    {
30538                        self.write(s);
30539                    }
30540                    _ => {
30541                        self.generate_expression(value)?;
30542                    }
30543                }
30544            }
30545            if let Some(ref unit_spec) = interval.unit {
30546                self.write_space();
30547                self.write_interval_unit_spec(unit_spec)?;
30548            }
30549            Ok(())
30550        } else {
30551            self.generate_expression(expr)
30552        }
30553    }
30554
30555    fn generate_partition_by_truncate(&mut self, e: &PartitionByTruncate) -> Result<()> {
30556        // TRUNCATE(expression, this)
30557        self.write_keyword("TRUNCATE");
30558        self.write("(");
30559        self.generate_expression(&e.expression)?;
30560        self.write(", ");
30561        self.generate_expression(&e.this)?;
30562        self.write(")");
30563        Ok(())
30564    }
30565
30566    fn generate_partition_list(&mut self, e: &PartitionList) -> Result<()> {
30567        // Doris: PARTITION name VALUES IN (val1, val2)
30568        self.write_keyword("PARTITION");
30569        self.write_space();
30570        self.generate_expression(&e.this)?;
30571        self.write_space();
30572        self.write_keyword("VALUES IN");
30573        self.write(" (");
30574        for (i, expr) in e.expressions.iter().enumerate() {
30575            if i > 0 {
30576                self.write(", ");
30577            }
30578            self.generate_expression(expr)?;
30579        }
30580        self.write(")");
30581        Ok(())
30582    }
30583
30584    fn generate_partition_range(&mut self, e: &PartitionRange) -> Result<()> {
30585        // Check if this is a TSQL-style simple range (e.g., "2 TO 5")
30586        // TSQL ranges have no expressions and just use `this TO expression`
30587        if e.expressions.is_empty() && e.expression.is_some() {
30588            // TSQL: simple range like "2 TO 5" - no PARTITION keyword
30589            self.generate_expression(&e.this)?;
30590            self.write_space();
30591            self.write_keyword("TO");
30592            self.write_space();
30593            self.generate_expression(e.expression.as_ref().unwrap())?;
30594            return Ok(());
30595        }
30596
30597        // Doris: PARTITION name VALUES LESS THAN (val) or PARTITION name VALUES [(val1), (val2))
30598        self.write_keyword("PARTITION");
30599        self.write_space();
30600        self.generate_expression(&e.this)?;
30601        self.write_space();
30602
30603        // Check if expressions contain Tuple (bracket notation) or single values (LESS THAN)
30604        if e.expressions.len() == 1 {
30605            // Single value: VALUES LESS THAN (val)
30606            self.write_keyword("VALUES LESS THAN");
30607            self.write(" (");
30608            self.generate_expression(&e.expressions[0])?;
30609            self.write(")");
30610        } else if !e.expressions.is_empty() {
30611            // Multiple values with Tuple: VALUES [(val1), (val2))
30612            self.write_keyword("VALUES");
30613            self.write(" [");
30614            for (i, expr) in e.expressions.iter().enumerate() {
30615                if i > 0 {
30616                    self.write(", ");
30617                }
30618                // If the expr is a Tuple, generate its contents wrapped in parens
30619                if let Expression::Tuple(t) = expr {
30620                    self.write("(");
30621                    for (j, inner) in t.expressions.iter().enumerate() {
30622                        if j > 0 {
30623                            self.write(", ");
30624                        }
30625                        self.generate_expression(inner)?;
30626                    }
30627                    self.write(")");
30628                } else {
30629                    self.write("(");
30630                    self.generate_expression(expr)?;
30631                    self.write(")");
30632                }
30633            }
30634            self.write(")");
30635        }
30636        Ok(())
30637    }
30638
30639    fn generate_partitioned_by_bucket(&mut self, e: &PartitionedByBucket) -> Result<()> {
30640        // BUCKET(this, expression)
30641        self.write_keyword("BUCKET");
30642        self.write("(");
30643        self.generate_expression(&e.this)?;
30644        self.write(", ");
30645        self.generate_expression(&e.expression)?;
30646        self.write(")");
30647        Ok(())
30648    }
30649
30650    fn generate_partitioned_by_property(&mut self, e: &PartitionedByProperty) -> Result<()> {
30651        // PARTITIONED BY this (Teradata/ClickHouse use PARTITION BY)
30652        if matches!(
30653            self.config.dialect,
30654            Some(crate::dialects::DialectType::Teradata)
30655                | Some(crate::dialects::DialectType::ClickHouse)
30656        ) {
30657            self.write_keyword("PARTITION BY");
30658        } else {
30659            self.write_keyword("PARTITIONED BY");
30660        }
30661        self.write_space();
30662        // In pretty mode, always use multiline tuple format for PARTITIONED BY
30663        if self.config.pretty {
30664            if let Expression::Tuple(ref tuple) = *e.this {
30665                self.write("(");
30666                self.write_newline();
30667                self.indent_level += 1;
30668                for (i, expr) in tuple.expressions.iter().enumerate() {
30669                    if i > 0 {
30670                        self.write(",");
30671                        self.write_newline();
30672                    }
30673                    self.write_indent();
30674                    self.generate_expression(expr)?;
30675                }
30676                self.indent_level -= 1;
30677                self.write_newline();
30678                self.write(")");
30679            } else {
30680                self.generate_expression(&e.this)?;
30681            }
30682        } else {
30683            self.generate_expression(&e.this)?;
30684        }
30685        Ok(())
30686    }
30687
30688    fn generate_partitioned_of_property(&mut self, e: &PartitionedOfProperty) -> Result<()> {
30689        // PARTITION OF this FOR VALUES expression or PARTITION OF this DEFAULT
30690        self.write_keyword("PARTITION OF");
30691        self.write_space();
30692        self.generate_expression(&e.this)?;
30693        // Check if expression is a PartitionBoundSpec
30694        if let Expression::PartitionBoundSpec(_) = e.expression.as_ref() {
30695            self.write_space();
30696            self.write_keyword("FOR VALUES");
30697            self.write_space();
30698            self.generate_expression(&e.expression)?;
30699        } else {
30700            self.write_space();
30701            self.write_keyword("DEFAULT");
30702        }
30703        Ok(())
30704    }
30705
30706    fn generate_period_for_system_time_constraint(
30707        &mut self,
30708        e: &PeriodForSystemTimeConstraint,
30709    ) -> Result<()> {
30710        // PERIOD FOR SYSTEM_TIME (this, expression)
30711        self.write_keyword("PERIOD FOR SYSTEM_TIME");
30712        self.write(" (");
30713        self.generate_expression(&e.this)?;
30714        self.write(", ");
30715        self.generate_expression(&e.expression)?;
30716        self.write(")");
30717        Ok(())
30718    }
30719
30720    fn generate_pivot_alias(&mut self, e: &PivotAlias) -> Result<()> {
30721        // value AS alias
30722        // The alias can be an identifier or an expression (e.g., string concatenation)
30723        self.generate_expression(&e.this)?;
30724        self.write_space();
30725        self.write_keyword("AS");
30726        self.write_space();
30727        // When target dialect uses identifiers for UNPIVOT aliases, convert literals to identifiers
30728        if self.config.unpivot_aliases_are_identifiers {
30729            match &e.alias {
30730                Expression::Literal(Literal::String(s)) => {
30731                    // Convert string literal to identifier
30732                    self.generate_identifier(&Identifier::new(s.clone()))?;
30733                }
30734                Expression::Literal(Literal::Number(n)) => {
30735                    // Convert number literal to quoted identifier
30736                    let mut id = Identifier::new(n.clone());
30737                    id.quoted = true;
30738                    self.generate_identifier(&id)?;
30739                }
30740                other => {
30741                    self.generate_expression(other)?;
30742                }
30743            }
30744        } else {
30745            self.generate_expression(&e.alias)?;
30746        }
30747        Ok(())
30748    }
30749
30750    fn generate_pivot_any(&mut self, e: &PivotAny) -> Result<()> {
30751        // ANY or ANY [expression]
30752        self.write_keyword("ANY");
30753        if let Some(this) = &e.this {
30754            self.write_space();
30755            self.generate_expression(this)?;
30756        }
30757        Ok(())
30758    }
30759
30760    fn generate_predict(&mut self, e: &Predict) -> Result<()> {
30761        // ML.PREDICT(MODEL this, expression, [params_struct])
30762        self.write_keyword("ML.PREDICT");
30763        self.write("(");
30764        self.write_keyword("MODEL");
30765        self.write_space();
30766        self.generate_expression(&e.this)?;
30767        self.write(", ");
30768        self.generate_expression(&e.expression)?;
30769        if let Some(params) = &e.params_struct {
30770            self.write(", ");
30771            self.generate_expression(params)?;
30772        }
30773        self.write(")");
30774        Ok(())
30775    }
30776
30777    fn generate_previous_day(&mut self, e: &PreviousDay) -> Result<()> {
30778        // PREVIOUS_DAY(this, expression)
30779        self.write_keyword("PREVIOUS_DAY");
30780        self.write("(");
30781        self.generate_expression(&e.this)?;
30782        self.write(", ");
30783        self.generate_expression(&e.expression)?;
30784        self.write(")");
30785        Ok(())
30786    }
30787
30788    fn generate_primary_key(&mut self, e: &PrimaryKey) -> Result<()> {
30789        // PRIMARY KEY [name] (columns) [INCLUDE (...)] [options]
30790        self.write_keyword("PRIMARY KEY");
30791        if let Some(name) = &e.this {
30792            self.write_space();
30793            self.generate_expression(name)?;
30794        }
30795        if !e.expressions.is_empty() {
30796            self.write(" (");
30797            for (i, expr) in e.expressions.iter().enumerate() {
30798                if i > 0 {
30799                    self.write(", ");
30800                }
30801                self.generate_expression(expr)?;
30802            }
30803            self.write(")");
30804        }
30805        if let Some(include) = &e.include {
30806            self.write_space();
30807            self.generate_expression(include)?;
30808        }
30809        if !e.options.is_empty() {
30810            self.write_space();
30811            for (i, opt) in e.options.iter().enumerate() {
30812                if i > 0 {
30813                    self.write_space();
30814                }
30815                self.generate_expression(opt)?;
30816            }
30817        }
30818        Ok(())
30819    }
30820
30821    fn generate_primary_key_column_constraint(
30822        &mut self,
30823        _e: &PrimaryKeyColumnConstraint,
30824    ) -> Result<()> {
30825        // PRIMARY KEY constraint at column level
30826        self.write_keyword("PRIMARY KEY");
30827        Ok(())
30828    }
30829
30830    fn generate_path_column_constraint(&mut self, e: &PathColumnConstraint) -> Result<()> {
30831        // PATH 'xpath' constraint for XMLTABLE/JSON_TABLE columns
30832        self.write_keyword("PATH");
30833        self.write_space();
30834        self.generate_expression(&e.this)?;
30835        Ok(())
30836    }
30837
30838    fn generate_projection_def(&mut self, e: &ProjectionDef) -> Result<()> {
30839        // PROJECTION this (expression)
30840        self.write_keyword("PROJECTION");
30841        self.write_space();
30842        self.generate_expression(&e.this)?;
30843        self.write(" (");
30844        self.generate_expression(&e.expression)?;
30845        self.write(")");
30846        Ok(())
30847    }
30848
30849    fn generate_properties(&mut self, e: &Properties) -> Result<()> {
30850        // Properties list
30851        for (i, prop) in e.expressions.iter().enumerate() {
30852            if i > 0 {
30853                self.write(", ");
30854            }
30855            self.generate_expression(prop)?;
30856        }
30857        Ok(())
30858    }
30859
30860    fn generate_property(&mut self, e: &Property) -> Result<()> {
30861        // name=value
30862        self.generate_expression(&e.this)?;
30863        if let Some(value) = &e.value {
30864            self.write("=");
30865            self.generate_expression(value)?;
30866        }
30867        Ok(())
30868    }
30869
30870    /// Generate BigQuery-style OPTIONS clause: OPTIONS (key=value, key=value, ...)
30871    fn generate_options_clause(&mut self, options: &[Expression]) -> Result<()> {
30872        self.write_keyword("OPTIONS");
30873        self.write(" (");
30874        for (i, opt) in options.iter().enumerate() {
30875            if i > 0 {
30876                self.write(", ");
30877            }
30878            self.generate_option_expression(opt)?;
30879        }
30880        self.write(")");
30881        Ok(())
30882    }
30883
30884    /// Generate Doris/StarRocks-style PROPERTIES clause: PROPERTIES ('key'='value', 'key'='value', ...)
30885    fn generate_properties_clause(&mut self, properties: &[Expression]) -> Result<()> {
30886        self.write_keyword("PROPERTIES");
30887        self.write(" (");
30888        for (i, prop) in properties.iter().enumerate() {
30889            if i > 0 {
30890                self.write(", ");
30891            }
30892            self.generate_option_expression(prop)?;
30893        }
30894        self.write(")");
30895        Ok(())
30896    }
30897
30898    /// Generate Databricks-style ENVIRONMENT clause: ENVIRONMENT (key = 'value', key = 'value', ...)
30899    fn generate_environment_clause(&mut self, environment: &[Expression]) -> Result<()> {
30900        self.write_keyword("ENVIRONMENT");
30901        self.write(" (");
30902        for (i, env_item) in environment.iter().enumerate() {
30903            if i > 0 {
30904                self.write(", ");
30905            }
30906            self.generate_environment_expression(env_item)?;
30907        }
30908        self.write(")");
30909        Ok(())
30910    }
30911
30912    /// Generate an environment expression with spaces around =
30913    fn generate_environment_expression(&mut self, expr: &Expression) -> Result<()> {
30914        match expr {
30915            Expression::Eq(eq) => {
30916                // Generate key = value with spaces (Databricks ENVIRONMENT style)
30917                self.generate_expression(&eq.left)?;
30918                self.write(" = ");
30919                self.generate_expression(&eq.right)?;
30920                Ok(())
30921            }
30922            _ => self.generate_expression(expr),
30923        }
30924    }
30925
30926    /// Generate Hive-style TBLPROPERTIES clause: TBLPROPERTIES ('key'='value', ...)
30927    fn generate_tblproperties_clause(&mut self, options: &[Expression]) -> Result<()> {
30928        self.write_keyword("TBLPROPERTIES");
30929        if self.config.pretty {
30930            self.write(" (");
30931            self.write_newline();
30932            self.indent_level += 1;
30933            for (i, opt) in options.iter().enumerate() {
30934                if i > 0 {
30935                    self.write(",");
30936                    self.write_newline();
30937                }
30938                self.write_indent();
30939                self.generate_option_expression(opt)?;
30940            }
30941            self.indent_level -= 1;
30942            self.write_newline();
30943            self.write(")");
30944        } else {
30945            self.write(" (");
30946            for (i, opt) in options.iter().enumerate() {
30947                if i > 0 {
30948                    self.write(", ");
30949                }
30950                self.generate_option_expression(opt)?;
30951            }
30952            self.write(")");
30953        }
30954        Ok(())
30955    }
30956
30957    /// Generate an option expression without spaces around =
30958    fn generate_option_expression(&mut self, expr: &Expression) -> Result<()> {
30959        match expr {
30960            Expression::Eq(eq) => {
30961                // Generate key=value without spaces
30962                self.generate_expression(&eq.left)?;
30963                self.write("=");
30964                self.generate_expression(&eq.right)?;
30965                Ok(())
30966            }
30967            _ => self.generate_expression(expr),
30968        }
30969    }
30970
30971    fn generate_pseudo_type(&mut self, e: &PseudoType) -> Result<()> {
30972        // Just output the name
30973        self.generate_expression(&e.this)?;
30974        Ok(())
30975    }
30976
30977    fn generate_put(&mut self, e: &PutStmt) -> Result<()> {
30978        // PUT source_file @stage [options]
30979        self.write_keyword("PUT");
30980        self.write_space();
30981
30982        // Source file path - preserve original quoting
30983        if e.source_quoted {
30984            self.write("'");
30985            self.write(&e.source);
30986            self.write("'");
30987        } else {
30988            self.write(&e.source);
30989        }
30990
30991        self.write_space();
30992
30993        // Target stage reference - output the string directly (includes @)
30994        if let Expression::Literal(Literal::String(s)) = &e.target {
30995            self.write(s);
30996        } else {
30997            self.generate_expression(&e.target)?;
30998        }
30999
31000        // Optional parameters: KEY=VALUE
31001        for param in &e.params {
31002            self.write_space();
31003            self.write(&param.name);
31004            if let Some(ref value) = param.value {
31005                self.write("=");
31006                self.generate_expression(value)?;
31007            }
31008        }
31009
31010        Ok(())
31011    }
31012
31013    fn generate_quantile(&mut self, e: &Quantile) -> Result<()> {
31014        // QUANTILE(this, quantile)
31015        self.write_keyword("QUANTILE");
31016        self.write("(");
31017        self.generate_expression(&e.this)?;
31018        if let Some(quantile) = &e.quantile {
31019            self.write(", ");
31020            self.generate_expression(quantile)?;
31021        }
31022        self.write(")");
31023        Ok(())
31024    }
31025
31026    fn generate_query_band(&mut self, e: &QueryBand) -> Result<()> {
31027        // QUERY_BAND = this [UPDATE] [FOR scope]
31028        if matches!(
31029            self.config.dialect,
31030            Some(crate::dialects::DialectType::Teradata)
31031        ) {
31032            self.write_keyword("SET");
31033            self.write_space();
31034        }
31035        self.write_keyword("QUERY_BAND");
31036        self.write(" = ");
31037        self.generate_expression(&e.this)?;
31038        if e.update.is_some() {
31039            self.write_space();
31040            self.write_keyword("UPDATE");
31041        }
31042        if let Some(scope) = &e.scope {
31043            self.write_space();
31044            self.write_keyword("FOR");
31045            self.write_space();
31046            self.generate_expression(scope)?;
31047        }
31048        Ok(())
31049    }
31050
31051    fn generate_query_option(&mut self, e: &QueryOption) -> Result<()> {
31052        // this = expression
31053        self.generate_expression(&e.this)?;
31054        if let Some(expression) = &e.expression {
31055            self.write(" = ");
31056            self.generate_expression(expression)?;
31057        }
31058        Ok(())
31059    }
31060
31061    fn generate_query_transform(&mut self, e: &QueryTransform) -> Result<()> {
31062        // TRANSFORM (expressions) [row_format_before] [RECORDWRITER record_writer] USING command_script [AS schema] [row_format_after] [RECORDREADER record_reader]
31063        self.write_keyword("TRANSFORM");
31064        self.write("(");
31065        for (i, expr) in e.expressions.iter().enumerate() {
31066            if i > 0 {
31067                self.write(", ");
31068            }
31069            self.generate_expression(expr)?;
31070        }
31071        self.write(")");
31072        if let Some(row_format_before) = &e.row_format_before {
31073            self.write_space();
31074            self.generate_expression(row_format_before)?;
31075        }
31076        if let Some(record_writer) = &e.record_writer {
31077            self.write_space();
31078            self.write_keyword("RECORDWRITER");
31079            self.write_space();
31080            self.generate_expression(record_writer)?;
31081        }
31082        if let Some(command_script) = &e.command_script {
31083            self.write_space();
31084            self.write_keyword("USING");
31085            self.write_space();
31086            self.generate_expression(command_script)?;
31087        }
31088        if let Some(schema) = &e.schema {
31089            self.write_space();
31090            self.write_keyword("AS");
31091            self.write_space();
31092            self.generate_expression(schema)?;
31093        }
31094        if let Some(row_format_after) = &e.row_format_after {
31095            self.write_space();
31096            self.generate_expression(row_format_after)?;
31097        }
31098        if let Some(record_reader) = &e.record_reader {
31099            self.write_space();
31100            self.write_keyword("RECORDREADER");
31101            self.write_space();
31102            self.generate_expression(record_reader)?;
31103        }
31104        Ok(())
31105    }
31106
31107    fn generate_randn(&mut self, e: &Randn) -> Result<()> {
31108        // RANDN([seed])
31109        self.write_keyword("RANDN");
31110        self.write("(");
31111        if let Some(this) = &e.this {
31112            self.generate_expression(this)?;
31113        }
31114        self.write(")");
31115        Ok(())
31116    }
31117
31118    fn generate_randstr(&mut self, e: &Randstr) -> Result<()> {
31119        // RANDSTR(this, [generator])
31120        self.write_keyword("RANDSTR");
31121        self.write("(");
31122        self.generate_expression(&e.this)?;
31123        if let Some(generator) = &e.generator {
31124            self.write(", ");
31125            self.generate_expression(generator)?;
31126        }
31127        self.write(")");
31128        Ok(())
31129    }
31130
31131    fn generate_range_bucket(&mut self, e: &RangeBucket) -> Result<()> {
31132        // RANGE_BUCKET(this, expression)
31133        self.write_keyword("RANGE_BUCKET");
31134        self.write("(");
31135        self.generate_expression(&e.this)?;
31136        self.write(", ");
31137        self.generate_expression(&e.expression)?;
31138        self.write(")");
31139        Ok(())
31140    }
31141
31142    fn generate_range_n(&mut self, e: &RangeN) -> Result<()> {
31143        // RANGE_N(this BETWEEN expressions [EACH each])
31144        self.write_keyword("RANGE_N");
31145        self.write("(");
31146        self.generate_expression(&e.this)?;
31147        self.write_space();
31148        self.write_keyword("BETWEEN");
31149        self.write_space();
31150        for (i, expr) in e.expressions.iter().enumerate() {
31151            if i > 0 {
31152                self.write(", ");
31153            }
31154            self.generate_expression(expr)?;
31155        }
31156        if let Some(each) = &e.each {
31157            self.write_space();
31158            self.write_keyword("EACH");
31159            self.write_space();
31160            self.generate_expression(each)?;
31161        }
31162        self.write(")");
31163        Ok(())
31164    }
31165
31166    fn generate_read_csv(&mut self, e: &ReadCSV) -> Result<()> {
31167        // READ_CSV(this, expressions...)
31168        self.write_keyword("READ_CSV");
31169        self.write("(");
31170        self.generate_expression(&e.this)?;
31171        for expr in &e.expressions {
31172            self.write(", ");
31173            self.generate_expression(expr)?;
31174        }
31175        self.write(")");
31176        Ok(())
31177    }
31178
31179    fn generate_read_parquet(&mut self, e: &ReadParquet) -> Result<()> {
31180        // READ_PARQUET(expressions...)
31181        self.write_keyword("READ_PARQUET");
31182        self.write("(");
31183        for (i, expr) in e.expressions.iter().enumerate() {
31184            if i > 0 {
31185                self.write(", ");
31186            }
31187            self.generate_expression(expr)?;
31188        }
31189        self.write(")");
31190        Ok(())
31191    }
31192
31193    fn generate_recursive_with_search(&mut self, e: &RecursiveWithSearch) -> Result<()> {
31194        // SEARCH kind FIRST BY this SET expression [USING using]
31195        // or CYCLE this SET expression [USING using]
31196        if e.kind == "CYCLE" {
31197            self.write_keyword("CYCLE");
31198        } else {
31199            self.write_keyword("SEARCH");
31200            self.write_space();
31201            self.write(&e.kind);
31202            self.write_space();
31203            self.write_keyword("FIRST BY");
31204        }
31205        self.write_space();
31206        self.generate_expression(&e.this)?;
31207        self.write_space();
31208        self.write_keyword("SET");
31209        self.write_space();
31210        self.generate_expression(&e.expression)?;
31211        if let Some(using) = &e.using {
31212            self.write_space();
31213            self.write_keyword("USING");
31214            self.write_space();
31215            self.generate_expression(using)?;
31216        }
31217        Ok(())
31218    }
31219
31220    fn generate_reduce(&mut self, e: &Reduce) -> Result<()> {
31221        // REDUCE(this, initial, merge, [finish])
31222        self.write_keyword("REDUCE");
31223        self.write("(");
31224        self.generate_expression(&e.this)?;
31225        if let Some(initial) = &e.initial {
31226            self.write(", ");
31227            self.generate_expression(initial)?;
31228        }
31229        if let Some(merge) = &e.merge {
31230            self.write(", ");
31231            self.generate_expression(merge)?;
31232        }
31233        if let Some(finish) = &e.finish {
31234            self.write(", ");
31235            self.generate_expression(finish)?;
31236        }
31237        self.write(")");
31238        Ok(())
31239    }
31240
31241    fn generate_reference(&mut self, e: &Reference) -> Result<()> {
31242        // REFERENCES this (expressions) [options]
31243        self.write_keyword("REFERENCES");
31244        self.write_space();
31245        self.generate_expression(&e.this)?;
31246        if !e.expressions.is_empty() {
31247            self.write(" (");
31248            for (i, expr) in e.expressions.iter().enumerate() {
31249                if i > 0 {
31250                    self.write(", ");
31251                }
31252                self.generate_expression(expr)?;
31253            }
31254            self.write(")");
31255        }
31256        for opt in &e.options {
31257            self.write_space();
31258            self.generate_expression(opt)?;
31259        }
31260        Ok(())
31261    }
31262
31263    fn generate_refresh(&mut self, e: &Refresh) -> Result<()> {
31264        // REFRESH [kind] this
31265        self.write_keyword("REFRESH");
31266        if !e.kind.is_empty() {
31267            self.write_space();
31268            self.write_keyword(&e.kind);
31269        }
31270        self.write_space();
31271        self.generate_expression(&e.this)?;
31272        Ok(())
31273    }
31274
31275    fn generate_refresh_trigger_property(&mut self, e: &RefreshTriggerProperty) -> Result<()> {
31276        // Doris REFRESH clause: REFRESH method ON kind [EVERY n UNIT] [STARTS 'datetime']
31277        self.write_keyword("REFRESH");
31278        self.write_space();
31279        self.write_keyword(&e.method);
31280
31281        if let Some(ref kind) = e.kind {
31282            self.write_space();
31283            self.write_keyword("ON");
31284            self.write_space();
31285            self.write_keyword(kind);
31286
31287            // EVERY n UNIT
31288            if let Some(ref every) = e.every {
31289                self.write_space();
31290                self.write_keyword("EVERY");
31291                self.write_space();
31292                self.generate_expression(every)?;
31293                if let Some(ref unit) = e.unit {
31294                    self.write_space();
31295                    self.write_keyword(unit);
31296                }
31297            }
31298
31299            // STARTS 'datetime'
31300            if let Some(ref starts) = e.starts {
31301                self.write_space();
31302                self.write_keyword("STARTS");
31303                self.write_space();
31304                self.generate_expression(starts)?;
31305            }
31306        }
31307        Ok(())
31308    }
31309
31310    fn generate_regexp_count(&mut self, e: &RegexpCount) -> Result<()> {
31311        // REGEXP_COUNT(this, expression, position, parameters)
31312        self.write_keyword("REGEXP_COUNT");
31313        self.write("(");
31314        self.generate_expression(&e.this)?;
31315        self.write(", ");
31316        self.generate_expression(&e.expression)?;
31317        if let Some(position) = &e.position {
31318            self.write(", ");
31319            self.generate_expression(position)?;
31320        }
31321        if let Some(parameters) = &e.parameters {
31322            self.write(", ");
31323            self.generate_expression(parameters)?;
31324        }
31325        self.write(")");
31326        Ok(())
31327    }
31328
31329    fn generate_regexp_extract_all(&mut self, e: &RegexpExtractAll) -> Result<()> {
31330        // REGEXP_EXTRACT_ALL(this, expression, group, parameters, position, occurrence)
31331        self.write_keyword("REGEXP_EXTRACT_ALL");
31332        self.write("(");
31333        self.generate_expression(&e.this)?;
31334        self.write(", ");
31335        self.generate_expression(&e.expression)?;
31336        if let Some(group) = &e.group {
31337            self.write(", ");
31338            self.generate_expression(group)?;
31339        }
31340        self.write(")");
31341        Ok(())
31342    }
31343
31344    fn generate_regexp_full_match(&mut self, e: &RegexpFullMatch) -> Result<()> {
31345        // REGEXP_FULL_MATCH(this, expression)
31346        self.write_keyword("REGEXP_FULL_MATCH");
31347        self.write("(");
31348        self.generate_expression(&e.this)?;
31349        self.write(", ");
31350        self.generate_expression(&e.expression)?;
31351        self.write(")");
31352        Ok(())
31353    }
31354
31355    fn generate_regexp_i_like(&mut self, e: &RegexpILike) -> Result<()> {
31356        use crate::dialects::DialectType;
31357        // PostgreSQL/Redshift uses ~* operator for case-insensitive regex matching
31358        if matches!(
31359            self.config.dialect,
31360            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
31361        ) && e.flag.is_none()
31362        {
31363            self.generate_expression(&e.this)?;
31364            self.write(" ~* ");
31365            self.generate_expression(&e.expression)?;
31366        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
31367            // Snowflake uses REGEXP_LIKE(x, pattern, 'i')
31368            self.write_keyword("REGEXP_LIKE");
31369            self.write("(");
31370            self.generate_expression(&e.this)?;
31371            self.write(", ");
31372            self.generate_expression(&e.expression)?;
31373            self.write(", ");
31374            if let Some(flag) = &e.flag {
31375                self.generate_expression(flag)?;
31376            } else {
31377                self.write("'i'");
31378            }
31379            self.write(")");
31380        } else {
31381            // this REGEXP_ILIKE expression or REGEXP_ILIKE(this, expression, flag)
31382            self.generate_expression(&e.this)?;
31383            self.write_space();
31384            self.write_keyword("REGEXP_ILIKE");
31385            self.write_space();
31386            self.generate_expression(&e.expression)?;
31387            if let Some(flag) = &e.flag {
31388                self.write(", ");
31389                self.generate_expression(flag)?;
31390            }
31391        }
31392        Ok(())
31393    }
31394
31395    fn generate_regexp_instr(&mut self, e: &RegexpInstr) -> Result<()> {
31396        // REGEXP_INSTR(this, expression, position, occurrence, option, parameters, group)
31397        self.write_keyword("REGEXP_INSTR");
31398        self.write("(");
31399        self.generate_expression(&e.this)?;
31400        self.write(", ");
31401        self.generate_expression(&e.expression)?;
31402        if let Some(position) = &e.position {
31403            self.write(", ");
31404            self.generate_expression(position)?;
31405        }
31406        if let Some(occurrence) = &e.occurrence {
31407            self.write(", ");
31408            self.generate_expression(occurrence)?;
31409        }
31410        if let Some(option) = &e.option {
31411            self.write(", ");
31412            self.generate_expression(option)?;
31413        }
31414        if let Some(parameters) = &e.parameters {
31415            self.write(", ");
31416            self.generate_expression(parameters)?;
31417        }
31418        if let Some(group) = &e.group {
31419            self.write(", ");
31420            self.generate_expression(group)?;
31421        }
31422        self.write(")");
31423        Ok(())
31424    }
31425
31426    fn generate_regexp_split(&mut self, e: &RegexpSplit) -> Result<()> {
31427        // REGEXP_SPLIT(this, expression, limit)
31428        self.write_keyword("REGEXP_SPLIT");
31429        self.write("(");
31430        self.generate_expression(&e.this)?;
31431        self.write(", ");
31432        self.generate_expression(&e.expression)?;
31433        if let Some(limit) = &e.limit {
31434            self.write(", ");
31435            self.generate_expression(limit)?;
31436        }
31437        self.write(")");
31438        Ok(())
31439    }
31440
31441    fn generate_regr_avgx(&mut self, e: &RegrAvgx) -> Result<()> {
31442        // REGR_AVGX(this, expression)
31443        self.write_keyword("REGR_AVGX");
31444        self.write("(");
31445        self.generate_expression(&e.this)?;
31446        self.write(", ");
31447        self.generate_expression(&e.expression)?;
31448        self.write(")");
31449        Ok(())
31450    }
31451
31452    fn generate_regr_avgy(&mut self, e: &RegrAvgy) -> Result<()> {
31453        // REGR_AVGY(this, expression)
31454        self.write_keyword("REGR_AVGY");
31455        self.write("(");
31456        self.generate_expression(&e.this)?;
31457        self.write(", ");
31458        self.generate_expression(&e.expression)?;
31459        self.write(")");
31460        Ok(())
31461    }
31462
31463    fn generate_regr_count(&mut self, e: &RegrCount) -> Result<()> {
31464        // REGR_COUNT(this, expression)
31465        self.write_keyword("REGR_COUNT");
31466        self.write("(");
31467        self.generate_expression(&e.this)?;
31468        self.write(", ");
31469        self.generate_expression(&e.expression)?;
31470        self.write(")");
31471        Ok(())
31472    }
31473
31474    fn generate_regr_intercept(&mut self, e: &RegrIntercept) -> Result<()> {
31475        // REGR_INTERCEPT(this, expression)
31476        self.write_keyword("REGR_INTERCEPT");
31477        self.write("(");
31478        self.generate_expression(&e.this)?;
31479        self.write(", ");
31480        self.generate_expression(&e.expression)?;
31481        self.write(")");
31482        Ok(())
31483    }
31484
31485    fn generate_regr_r2(&mut self, e: &RegrR2) -> Result<()> {
31486        // REGR_R2(this, expression)
31487        self.write_keyword("REGR_R2");
31488        self.write("(");
31489        self.generate_expression(&e.this)?;
31490        self.write(", ");
31491        self.generate_expression(&e.expression)?;
31492        self.write(")");
31493        Ok(())
31494    }
31495
31496    fn generate_regr_slope(&mut self, e: &RegrSlope) -> Result<()> {
31497        // REGR_SLOPE(this, expression)
31498        self.write_keyword("REGR_SLOPE");
31499        self.write("(");
31500        self.generate_expression(&e.this)?;
31501        self.write(", ");
31502        self.generate_expression(&e.expression)?;
31503        self.write(")");
31504        Ok(())
31505    }
31506
31507    fn generate_regr_sxx(&mut self, e: &RegrSxx) -> Result<()> {
31508        // REGR_SXX(this, expression)
31509        self.write_keyword("REGR_SXX");
31510        self.write("(");
31511        self.generate_expression(&e.this)?;
31512        self.write(", ");
31513        self.generate_expression(&e.expression)?;
31514        self.write(")");
31515        Ok(())
31516    }
31517
31518    fn generate_regr_sxy(&mut self, e: &RegrSxy) -> Result<()> {
31519        // REGR_SXY(this, expression)
31520        self.write_keyword("REGR_SXY");
31521        self.write("(");
31522        self.generate_expression(&e.this)?;
31523        self.write(", ");
31524        self.generate_expression(&e.expression)?;
31525        self.write(")");
31526        Ok(())
31527    }
31528
31529    fn generate_regr_syy(&mut self, e: &RegrSyy) -> Result<()> {
31530        // REGR_SYY(this, expression)
31531        self.write_keyword("REGR_SYY");
31532        self.write("(");
31533        self.generate_expression(&e.this)?;
31534        self.write(", ");
31535        self.generate_expression(&e.expression)?;
31536        self.write(")");
31537        Ok(())
31538    }
31539
31540    fn generate_regr_valx(&mut self, e: &RegrValx) -> Result<()> {
31541        // REGR_VALX(this, expression)
31542        self.write_keyword("REGR_VALX");
31543        self.write("(");
31544        self.generate_expression(&e.this)?;
31545        self.write(", ");
31546        self.generate_expression(&e.expression)?;
31547        self.write(")");
31548        Ok(())
31549    }
31550
31551    fn generate_regr_valy(&mut self, e: &RegrValy) -> Result<()> {
31552        // REGR_VALY(this, expression)
31553        self.write_keyword("REGR_VALY");
31554        self.write("(");
31555        self.generate_expression(&e.this)?;
31556        self.write(", ");
31557        self.generate_expression(&e.expression)?;
31558        self.write(")");
31559        Ok(())
31560    }
31561
31562    fn generate_remote_with_connection_model_property(
31563        &mut self,
31564        e: &RemoteWithConnectionModelProperty,
31565    ) -> Result<()> {
31566        // REMOTE WITH CONNECTION this
31567        self.write_keyword("REMOTE WITH CONNECTION");
31568        self.write_space();
31569        self.generate_expression(&e.this)?;
31570        Ok(())
31571    }
31572
31573    fn generate_rename_column(&mut self, e: &RenameColumn) -> Result<()> {
31574        // RENAME COLUMN [IF EXISTS] this TO new_name
31575        self.write_keyword("RENAME COLUMN");
31576        if e.exists {
31577            self.write_space();
31578            self.write_keyword("IF EXISTS");
31579        }
31580        self.write_space();
31581        self.generate_expression(&e.this)?;
31582        if let Some(to) = &e.to {
31583            self.write_space();
31584            self.write_keyword("TO");
31585            self.write_space();
31586            self.generate_expression(to)?;
31587        }
31588        Ok(())
31589    }
31590
31591    fn generate_replace_partition(&mut self, e: &ReplacePartition) -> Result<()> {
31592        // REPLACE PARTITION expression [FROM source]
31593        self.write_keyword("REPLACE PARTITION");
31594        self.write_space();
31595        self.generate_expression(&e.expression)?;
31596        if let Some(source) = &e.source {
31597            self.write_space();
31598            self.write_keyword("FROM");
31599            self.write_space();
31600            self.generate_expression(source)?;
31601        }
31602        Ok(())
31603    }
31604
31605    fn generate_returning(&mut self, e: &Returning) -> Result<()> {
31606        // RETURNING expressions [INTO into]
31607        // TSQL and Fabric use OUTPUT instead of RETURNING
31608        let keyword = match self.config.dialect {
31609            Some(DialectType::TSQL) | Some(DialectType::Fabric) => "OUTPUT",
31610            _ => "RETURNING",
31611        };
31612        self.write_keyword(keyword);
31613        self.write_space();
31614        for (i, expr) in e.expressions.iter().enumerate() {
31615            if i > 0 {
31616                self.write(", ");
31617            }
31618            self.generate_expression(expr)?;
31619        }
31620        if let Some(into) = &e.into {
31621            self.write_space();
31622            self.write_keyword("INTO");
31623            self.write_space();
31624            self.generate_expression(into)?;
31625        }
31626        Ok(())
31627    }
31628
31629    fn generate_output_clause(&mut self, output: &OutputClause) -> Result<()> {
31630        // OUTPUT expressions [INTO into_table]
31631        self.write_space();
31632        self.write_keyword("OUTPUT");
31633        self.write_space();
31634        for (i, expr) in output.columns.iter().enumerate() {
31635            if i > 0 {
31636                self.write(", ");
31637            }
31638            self.generate_expression(expr)?;
31639        }
31640        if let Some(into_table) = &output.into_table {
31641            self.write_space();
31642            self.write_keyword("INTO");
31643            self.write_space();
31644            self.generate_expression(into_table)?;
31645        }
31646        Ok(())
31647    }
31648
31649    fn generate_returns_property(&mut self, e: &ReturnsProperty) -> Result<()> {
31650        // RETURNS [TABLE] this [NULL ON NULL INPUT | CALLED ON NULL INPUT]
31651        self.write_keyword("RETURNS");
31652        if e.is_table.is_some() {
31653            self.write_space();
31654            self.write_keyword("TABLE");
31655        }
31656        if let Some(table) = &e.table {
31657            self.write_space();
31658            self.generate_expression(table)?;
31659        } else if let Some(this) = &e.this {
31660            self.write_space();
31661            self.generate_expression(this)?;
31662        }
31663        if e.null.is_some() {
31664            self.write_space();
31665            self.write_keyword("NULL ON NULL INPUT");
31666        }
31667        Ok(())
31668    }
31669
31670    fn generate_rollback(&mut self, e: &Rollback) -> Result<()> {
31671        // ROLLBACK [TRANSACTION [transaction_name]] [TO savepoint]
31672        self.write_keyword("ROLLBACK");
31673
31674        // TSQL always uses ROLLBACK TRANSACTION
31675        if e.this.is_none()
31676            && matches!(
31677                self.config.dialect,
31678                Some(DialectType::TSQL) | Some(DialectType::Fabric)
31679            )
31680        {
31681            self.write_space();
31682            self.write_keyword("TRANSACTION");
31683        }
31684
31685        // Check if this has TRANSACTION keyword or transaction name
31686        if let Some(this) = &e.this {
31687            // Check if it's just the "TRANSACTION" marker or an actual transaction name
31688            let is_transaction_marker = matches!(
31689                this.as_ref(),
31690                Expression::Identifier(id) if id.name == "TRANSACTION"
31691            );
31692
31693            self.write_space();
31694            self.write_keyword("TRANSACTION");
31695
31696            // If it's a real transaction name, output it
31697            if !is_transaction_marker {
31698                self.write_space();
31699                self.generate_expression(this)?;
31700            }
31701        }
31702
31703        // Output TO savepoint
31704        if let Some(savepoint) = &e.savepoint {
31705            self.write_space();
31706            self.write_keyword("TO");
31707            self.write_space();
31708            self.generate_expression(savepoint)?;
31709        }
31710        Ok(())
31711    }
31712
31713    fn generate_rollup(&mut self, e: &Rollup) -> Result<()> {
31714        // Python: return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
31715        if e.expressions.is_empty() {
31716            self.write_keyword("WITH ROLLUP");
31717        } else {
31718            self.write_keyword("ROLLUP");
31719            self.write("(");
31720            for (i, expr) in e.expressions.iter().enumerate() {
31721                if i > 0 {
31722                    self.write(", ");
31723                }
31724                self.generate_expression(expr)?;
31725            }
31726            self.write(")");
31727        }
31728        Ok(())
31729    }
31730
31731    fn generate_row_format_delimited_property(
31732        &mut self,
31733        e: &RowFormatDelimitedProperty,
31734    ) -> Result<()> {
31735        // ROW FORMAT DELIMITED [FIELDS TERMINATED BY ...] [ESCAPED BY ...] [COLLECTION ITEMS TERMINATED BY ...] [MAP KEYS TERMINATED BY ...] [LINES TERMINATED BY ...] [NULL DEFINED AS ...]
31736        self.write_keyword("ROW FORMAT DELIMITED");
31737        if let Some(fields) = &e.fields {
31738            self.write_space();
31739            self.write_keyword("FIELDS TERMINATED BY");
31740            self.write_space();
31741            self.generate_expression(fields)?;
31742        }
31743        if let Some(escaped) = &e.escaped {
31744            self.write_space();
31745            self.write_keyword("ESCAPED BY");
31746            self.write_space();
31747            self.generate_expression(escaped)?;
31748        }
31749        if let Some(items) = &e.collection_items {
31750            self.write_space();
31751            self.write_keyword("COLLECTION ITEMS TERMINATED BY");
31752            self.write_space();
31753            self.generate_expression(items)?;
31754        }
31755        if let Some(keys) = &e.map_keys {
31756            self.write_space();
31757            self.write_keyword("MAP KEYS TERMINATED BY");
31758            self.write_space();
31759            self.generate_expression(keys)?;
31760        }
31761        if let Some(lines) = &e.lines {
31762            self.write_space();
31763            self.write_keyword("LINES TERMINATED BY");
31764            self.write_space();
31765            self.generate_expression(lines)?;
31766        }
31767        if let Some(null) = &e.null {
31768            self.write_space();
31769            self.write_keyword("NULL DEFINED AS");
31770            self.write_space();
31771            self.generate_expression(null)?;
31772        }
31773        if let Some(serde) = &e.serde {
31774            self.write_space();
31775            self.generate_expression(serde)?;
31776        }
31777        Ok(())
31778    }
31779
31780    fn generate_row_format_property(&mut self, e: &RowFormatProperty) -> Result<()> {
31781        // ROW FORMAT this
31782        self.write_keyword("ROW FORMAT");
31783        self.write_space();
31784        self.generate_expression(&e.this)?;
31785        Ok(())
31786    }
31787
31788    fn generate_row_format_serde_property(&mut self, e: &RowFormatSerdeProperty) -> Result<()> {
31789        // ROW FORMAT SERDE this [WITH SERDEPROPERTIES (...)]
31790        self.write_keyword("ROW FORMAT SERDE");
31791        self.write_space();
31792        self.generate_expression(&e.this)?;
31793        if let Some(props) = &e.serde_properties {
31794            self.write_space();
31795            // SerdeProperties generates its own "[WITH] SERDEPROPERTIES (...)"
31796            self.generate_expression(props)?;
31797        }
31798        Ok(())
31799    }
31800
31801    fn generate_sha2(&mut self, e: &SHA2) -> Result<()> {
31802        // SHA2(this, length)
31803        self.write_keyword("SHA2");
31804        self.write("(");
31805        self.generate_expression(&e.this)?;
31806        if let Some(length) = e.length {
31807            self.write(", ");
31808            self.write(&length.to_string());
31809        }
31810        self.write(")");
31811        Ok(())
31812    }
31813
31814    fn generate_sha2_digest(&mut self, e: &SHA2Digest) -> Result<()> {
31815        // SHA2_DIGEST(this, length)
31816        self.write_keyword("SHA2_DIGEST");
31817        self.write("(");
31818        self.generate_expression(&e.this)?;
31819        if let Some(length) = e.length {
31820            self.write(", ");
31821            self.write(&length.to_string());
31822        }
31823        self.write(")");
31824        Ok(())
31825    }
31826
31827    fn generate_safe_add(&mut self, e: &SafeAdd) -> Result<()> {
31828        let name = if matches!(
31829            self.config.dialect,
31830            Some(crate::dialects::DialectType::Spark)
31831                | Some(crate::dialects::DialectType::Databricks)
31832        ) {
31833            "TRY_ADD"
31834        } else {
31835            "SAFE_ADD"
31836        };
31837        self.write_keyword(name);
31838        self.write("(");
31839        self.generate_expression(&e.this)?;
31840        self.write(", ");
31841        self.generate_expression(&e.expression)?;
31842        self.write(")");
31843        Ok(())
31844    }
31845
31846    fn generate_safe_divide(&mut self, e: &SafeDivide) -> Result<()> {
31847        // SAFE_DIVIDE(this, expression)
31848        self.write_keyword("SAFE_DIVIDE");
31849        self.write("(");
31850        self.generate_expression(&e.this)?;
31851        self.write(", ");
31852        self.generate_expression(&e.expression)?;
31853        self.write(")");
31854        Ok(())
31855    }
31856
31857    fn generate_safe_multiply(&mut self, e: &SafeMultiply) -> Result<()> {
31858        let name = if matches!(
31859            self.config.dialect,
31860            Some(crate::dialects::DialectType::Spark)
31861                | Some(crate::dialects::DialectType::Databricks)
31862        ) {
31863            "TRY_MULTIPLY"
31864        } else {
31865            "SAFE_MULTIPLY"
31866        };
31867        self.write_keyword(name);
31868        self.write("(");
31869        self.generate_expression(&e.this)?;
31870        self.write(", ");
31871        self.generate_expression(&e.expression)?;
31872        self.write(")");
31873        Ok(())
31874    }
31875
31876    fn generate_safe_subtract(&mut self, e: &SafeSubtract) -> Result<()> {
31877        let name = if matches!(
31878            self.config.dialect,
31879            Some(crate::dialects::DialectType::Spark)
31880                | Some(crate::dialects::DialectType::Databricks)
31881        ) {
31882            "TRY_SUBTRACT"
31883        } else {
31884            "SAFE_SUBTRACT"
31885        };
31886        self.write_keyword(name);
31887        self.write("(");
31888        self.generate_expression(&e.this)?;
31889        self.write(", ");
31890        self.generate_expression(&e.expression)?;
31891        self.write(")");
31892        Ok(())
31893    }
31894
31895    /// Generate the body of a USING SAMPLE or TABLESAMPLE clause:
31896    /// METHOD (size UNIT) [REPEATABLE (seed)]
31897    fn generate_sample_body(&mut self, sample: &Sample) -> Result<()> {
31898        // Handle BUCKET sampling: TABLESAMPLE (BUCKET n OUT OF m [ON col])
31899        if matches!(sample.method, SampleMethod::Bucket) {
31900            self.write(" (");
31901            self.write_keyword("BUCKET");
31902            self.write_space();
31903            if let Some(ref num) = sample.bucket_numerator {
31904                self.generate_expression(num)?;
31905            }
31906            self.write_space();
31907            self.write_keyword("OUT OF");
31908            self.write_space();
31909            if let Some(ref denom) = sample.bucket_denominator {
31910                self.generate_expression(denom)?;
31911            }
31912            if let Some(ref field) = sample.bucket_field {
31913                self.write_space();
31914                self.write_keyword("ON");
31915                self.write_space();
31916                self.generate_expression(field)?;
31917            }
31918            self.write(")");
31919            return Ok(());
31920        }
31921
31922        // Output method name if explicitly specified, or for dialects that always require it
31923        let is_snowflake = matches!(
31924            self.config.dialect,
31925            Some(crate::dialects::DialectType::Snowflake)
31926        );
31927        let is_postgres = matches!(
31928            self.config.dialect,
31929            Some(crate::dialects::DialectType::PostgreSQL)
31930                | Some(crate::dialects::DialectType::Redshift)
31931        );
31932        // Databricks and Spark don't output method names
31933        let is_databricks = matches!(
31934            self.config.dialect,
31935            Some(crate::dialects::DialectType::Databricks)
31936        );
31937        let is_spark = matches!(
31938            self.config.dialect,
31939            Some(crate::dialects::DialectType::Spark)
31940        );
31941        let suppress_method = is_databricks || is_spark || sample.suppress_method_output;
31942        // PostgreSQL always outputs BERNOULLI for BERNOULLI samples
31943        let force_method = is_postgres && matches!(sample.method, SampleMethod::Bernoulli);
31944        if !suppress_method && (sample.explicit_method || is_snowflake || force_method) {
31945            self.write_space();
31946            if !sample.explicit_method && (is_snowflake || force_method) {
31947                // Snowflake/PostgreSQL defaults to BERNOULLI when no method is specified
31948                self.write_keyword("BERNOULLI");
31949            } else {
31950                match sample.method {
31951                    SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
31952                    SampleMethod::System => self.write_keyword("SYSTEM"),
31953                    SampleMethod::Block => self.write_keyword("BLOCK"),
31954                    SampleMethod::Row => self.write_keyword("ROW"),
31955                    SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
31956                    SampleMethod::Percent => self.write_keyword("SYSTEM"),
31957                    SampleMethod::Bucket => {} // handled above
31958                }
31959            }
31960        }
31961
31962        // Output size, with or without parentheses depending on dialect
31963        let emit_size_no_parens = !self.config.tablesample_requires_parens;
31964        if emit_size_no_parens {
31965            self.write_space();
31966            match &sample.size {
31967                Expression::Tuple(tuple) => {
31968                    for (i, expr) in tuple.expressions.iter().enumerate() {
31969                        if i > 0 {
31970                            self.write(", ");
31971                        }
31972                        self.generate_expression(expr)?;
31973                    }
31974                }
31975                expr => self.generate_expression(expr)?,
31976            }
31977        } else {
31978            self.write(" (");
31979            self.generate_expression(&sample.size)?;
31980        }
31981
31982        // Determine unit
31983        let is_rows_method = matches!(
31984            sample.method,
31985            SampleMethod::Reservoir | SampleMethod::Row | SampleMethod::Bucket
31986        );
31987        let is_percent = matches!(
31988            sample.method,
31989            SampleMethod::Percent
31990                | SampleMethod::System
31991                | SampleMethod::Bernoulli
31992                | SampleMethod::Block
31993        );
31994
31995        // For Snowflake, PostgreSQL, and Presto/Trino, only output ROWS/PERCENT when the user explicitly wrote it (unit_after_size).
31996        // These dialects use bare numbers for percentage by default in TABLESAMPLE METHOD(size) syntax.
31997        // For Databricks and Spark, always output PERCENT for percentage samples.
31998        let is_presto = matches!(
31999            self.config.dialect,
32000            Some(crate::dialects::DialectType::Presto)
32001                | Some(crate::dialects::DialectType::Trino)
32002                | Some(crate::dialects::DialectType::Athena)
32003        );
32004        let should_output_unit = if is_databricks || is_spark {
32005            // Always output PERCENT for percentage-based methods, or ROWS for row-based methods
32006            is_percent || is_rows_method || sample.unit_after_size
32007        } else if is_snowflake || is_postgres || is_presto {
32008            sample.unit_after_size
32009        } else {
32010            sample.unit_after_size || (sample.explicit_method && (is_rows_method || is_percent))
32011        };
32012
32013        if should_output_unit {
32014            self.write_space();
32015            if sample.is_percent {
32016                self.write_keyword("PERCENT");
32017            } else if is_rows_method && !sample.unit_after_size {
32018                self.write_keyword("ROWS");
32019            } else if sample.unit_after_size {
32020                match sample.method {
32021                    SampleMethod::Percent
32022                    | SampleMethod::System
32023                    | SampleMethod::Bernoulli
32024                    | SampleMethod::Block => {
32025                        self.write_keyword("PERCENT");
32026                    }
32027                    SampleMethod::Row | SampleMethod::Reservoir => {
32028                        self.write_keyword("ROWS");
32029                    }
32030                    _ => self.write_keyword("ROWS"),
32031                }
32032            } else {
32033                self.write_keyword("PERCENT");
32034            }
32035        }
32036
32037        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
32038            if let Some(ref offset) = sample.offset {
32039                self.write_space();
32040                self.write_keyword("OFFSET");
32041                self.write_space();
32042                self.generate_expression(offset)?;
32043            }
32044        }
32045        if !emit_size_no_parens {
32046            self.write(")");
32047        }
32048
32049        Ok(())
32050    }
32051
32052    fn generate_sample_property(&mut self, e: &SampleProperty) -> Result<()> {
32053        // SAMPLE this (ClickHouse uses SAMPLE BY)
32054        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
32055            self.write_keyword("SAMPLE BY");
32056        } else {
32057            self.write_keyword("SAMPLE");
32058        }
32059        self.write_space();
32060        self.generate_expression(&e.this)?;
32061        Ok(())
32062    }
32063
32064    fn generate_schema(&mut self, e: &Schema) -> Result<()> {
32065        // this (expressions...)
32066        if let Some(this) = &e.this {
32067            self.generate_expression(this)?;
32068        }
32069        if !e.expressions.is_empty() {
32070            // Add space before column list if there's a preceding expression
32071            if e.this.is_some() {
32072                self.write_space();
32073            }
32074            self.write("(");
32075            for (i, expr) in e.expressions.iter().enumerate() {
32076                if i > 0 {
32077                    self.write(", ");
32078                }
32079                self.generate_expression(expr)?;
32080            }
32081            self.write(")");
32082        }
32083        Ok(())
32084    }
32085
32086    fn generate_schema_comment_property(&mut self, e: &SchemaCommentProperty) -> Result<()> {
32087        // COMMENT this
32088        self.write_keyword("COMMENT");
32089        self.write_space();
32090        self.generate_expression(&e.this)?;
32091        Ok(())
32092    }
32093
32094    fn generate_scope_resolution(&mut self, e: &ScopeResolution) -> Result<()> {
32095        // [this::]expression
32096        if let Some(this) = &e.this {
32097            self.generate_expression(this)?;
32098            self.write("::");
32099        }
32100        self.generate_expression(&e.expression)?;
32101        Ok(())
32102    }
32103
32104    fn generate_search(&mut self, e: &Search) -> Result<()> {
32105        // SEARCH(this, expression, [json_scope], [analyzer], [analyzer_options], [search_mode])
32106        self.write_keyword("SEARCH");
32107        self.write("(");
32108        self.generate_expression(&e.this)?;
32109        self.write(", ");
32110        self.generate_expression(&e.expression)?;
32111        if let Some(json_scope) = &e.json_scope {
32112            self.write(", ");
32113            self.generate_expression(json_scope)?;
32114        }
32115        if let Some(analyzer) = &e.analyzer {
32116            self.write(", ");
32117            self.generate_expression(analyzer)?;
32118        }
32119        if let Some(analyzer_options) = &e.analyzer_options {
32120            self.write(", ");
32121            self.generate_expression(analyzer_options)?;
32122        }
32123        if let Some(search_mode) = &e.search_mode {
32124            self.write(", ");
32125            self.generate_expression(search_mode)?;
32126        }
32127        self.write(")");
32128        Ok(())
32129    }
32130
32131    fn generate_search_ip(&mut self, e: &SearchIp) -> Result<()> {
32132        // SEARCH_IP(this, expression)
32133        self.write_keyword("SEARCH_IP");
32134        self.write("(");
32135        self.generate_expression(&e.this)?;
32136        self.write(", ");
32137        self.generate_expression(&e.expression)?;
32138        self.write(")");
32139        Ok(())
32140    }
32141
32142    fn generate_security_property(&mut self, e: &SecurityProperty) -> Result<()> {
32143        // SECURITY this
32144        self.write_keyword("SECURITY");
32145        self.write_space();
32146        self.generate_expression(&e.this)?;
32147        Ok(())
32148    }
32149
32150    fn generate_semantic_view(&mut self, e: &SemanticView) -> Result<()> {
32151        // SEMANTIC_VIEW(this [METRICS ...] [DIMENSIONS ...] [FACTS ...] [WHERE ...])
32152        self.write("SEMANTIC_VIEW(");
32153
32154        if self.config.pretty {
32155            // Pretty print: each clause on its own line
32156            self.write_newline();
32157            self.indent_level += 1;
32158            self.write_indent();
32159            self.generate_expression(&e.this)?;
32160
32161            if let Some(metrics) = &e.metrics {
32162                self.write_newline();
32163                self.write_indent();
32164                self.write_keyword("METRICS");
32165                self.write_space();
32166                self.generate_semantic_view_tuple(metrics)?;
32167            }
32168            if let Some(dimensions) = &e.dimensions {
32169                self.write_newline();
32170                self.write_indent();
32171                self.write_keyword("DIMENSIONS");
32172                self.write_space();
32173                self.generate_semantic_view_tuple(dimensions)?;
32174            }
32175            if let Some(facts) = &e.facts {
32176                self.write_newline();
32177                self.write_indent();
32178                self.write_keyword("FACTS");
32179                self.write_space();
32180                self.generate_semantic_view_tuple(facts)?;
32181            }
32182            if let Some(where_) = &e.where_ {
32183                self.write_newline();
32184                self.write_indent();
32185                self.write_keyword("WHERE");
32186                self.write_space();
32187                self.generate_expression(where_)?;
32188            }
32189            self.write_newline();
32190            self.indent_level -= 1;
32191            self.write_indent();
32192        } else {
32193            // Compact: all on one line
32194            self.generate_expression(&e.this)?;
32195            if let Some(metrics) = &e.metrics {
32196                self.write_space();
32197                self.write_keyword("METRICS");
32198                self.write_space();
32199                self.generate_semantic_view_tuple(metrics)?;
32200            }
32201            if let Some(dimensions) = &e.dimensions {
32202                self.write_space();
32203                self.write_keyword("DIMENSIONS");
32204                self.write_space();
32205                self.generate_semantic_view_tuple(dimensions)?;
32206            }
32207            if let Some(facts) = &e.facts {
32208                self.write_space();
32209                self.write_keyword("FACTS");
32210                self.write_space();
32211                self.generate_semantic_view_tuple(facts)?;
32212            }
32213            if let Some(where_) = &e.where_ {
32214                self.write_space();
32215                self.write_keyword("WHERE");
32216                self.write_space();
32217                self.generate_expression(where_)?;
32218            }
32219        }
32220        self.write(")");
32221        Ok(())
32222    }
32223
32224    /// Helper for SEMANTIC_VIEW tuple contents (without parentheses)
32225    fn generate_semantic_view_tuple(&mut self, expr: &Expression) -> Result<()> {
32226        if let Expression::Tuple(t) = expr {
32227            for (i, e) in t.expressions.iter().enumerate() {
32228                if i > 0 {
32229                    self.write(", ");
32230                }
32231                self.generate_expression(e)?;
32232            }
32233        } else {
32234            self.generate_expression(expr)?;
32235        }
32236        Ok(())
32237    }
32238
32239    fn generate_sequence_properties(&mut self, e: &SequenceProperties) -> Result<()> {
32240        // [START WITH start] [INCREMENT BY increment] [MINVALUE minvalue] [MAXVALUE maxvalue] [CACHE cache] [OWNED BY owned]
32241        if let Some(start) = &e.start {
32242            self.write_keyword("START WITH");
32243            self.write_space();
32244            self.generate_expression(start)?;
32245        }
32246        if let Some(increment) = &e.increment {
32247            self.write_space();
32248            self.write_keyword("INCREMENT BY");
32249            self.write_space();
32250            self.generate_expression(increment)?;
32251        }
32252        if let Some(minvalue) = &e.minvalue {
32253            self.write_space();
32254            self.write_keyword("MINVALUE");
32255            self.write_space();
32256            self.generate_expression(minvalue)?;
32257        }
32258        if let Some(maxvalue) = &e.maxvalue {
32259            self.write_space();
32260            self.write_keyword("MAXVALUE");
32261            self.write_space();
32262            self.generate_expression(maxvalue)?;
32263        }
32264        if let Some(cache) = &e.cache {
32265            self.write_space();
32266            self.write_keyword("CACHE");
32267            self.write_space();
32268            self.generate_expression(cache)?;
32269        }
32270        if let Some(owned) = &e.owned {
32271            self.write_space();
32272            self.write_keyword("OWNED BY");
32273            self.write_space();
32274            self.generate_expression(owned)?;
32275        }
32276        for opt in &e.options {
32277            self.write_space();
32278            self.generate_expression(opt)?;
32279        }
32280        Ok(())
32281    }
32282
32283    fn generate_serde_properties(&mut self, e: &SerdeProperties) -> Result<()> {
32284        // [WITH] SERDEPROPERTIES (expressions)
32285        if e.with_.is_some() {
32286            self.write_keyword("WITH");
32287            self.write_space();
32288        }
32289        self.write_keyword("SERDEPROPERTIES");
32290        self.write(" (");
32291        for (i, expr) in e.expressions.iter().enumerate() {
32292            if i > 0 {
32293                self.write(", ");
32294            }
32295            // Generate key=value without spaces around =
32296            match expr {
32297                Expression::Eq(eq) => {
32298                    self.generate_expression(&eq.left)?;
32299                    self.write("=");
32300                    self.generate_expression(&eq.right)?;
32301                }
32302                _ => self.generate_expression(expr)?,
32303            }
32304        }
32305        self.write(")");
32306        Ok(())
32307    }
32308
32309    fn generate_session_parameter(&mut self, e: &SessionParameter) -> Result<()> {
32310        // @@[kind.]this
32311        self.write("@@");
32312        if let Some(kind) = &e.kind {
32313            self.write(kind);
32314            self.write(".");
32315        }
32316        self.generate_expression(&e.this)?;
32317        Ok(())
32318    }
32319
32320    fn generate_set(&mut self, e: &Set) -> Result<()> {
32321        // SET/UNSET [TAG] expressions
32322        if e.unset.is_some() {
32323            self.write_keyword("UNSET");
32324        } else {
32325            self.write_keyword("SET");
32326        }
32327        if e.tag.is_some() {
32328            self.write_space();
32329            self.write_keyword("TAG");
32330        }
32331        if !e.expressions.is_empty() {
32332            self.write_space();
32333            for (i, expr) in e.expressions.iter().enumerate() {
32334                if i > 0 {
32335                    self.write(", ");
32336                }
32337                self.generate_expression(expr)?;
32338            }
32339        }
32340        Ok(())
32341    }
32342
32343    fn generate_set_config_property(&mut self, e: &SetConfigProperty) -> Result<()> {
32344        // SET this or SETCONFIG this
32345        self.write_keyword("SET");
32346        self.write_space();
32347        self.generate_expression(&e.this)?;
32348        Ok(())
32349    }
32350
32351    fn generate_set_item(&mut self, e: &SetItem) -> Result<()> {
32352        // [kind] name = value
32353        if let Some(kind) = &e.kind {
32354            self.write_keyword(kind);
32355            self.write_space();
32356        }
32357        self.generate_expression(&e.name)?;
32358        self.write(" = ");
32359        self.generate_expression(&e.value)?;
32360        Ok(())
32361    }
32362
32363    fn generate_set_operation(&mut self, e: &SetOperation) -> Result<()> {
32364        // [WITH ...] this UNION|INTERSECT|EXCEPT [ALL|DISTINCT] [BY NAME] expression
32365        if let Some(with_) = &e.with_ {
32366            self.generate_expression(with_)?;
32367            self.write_space();
32368        }
32369        self.generate_expression(&e.this)?;
32370        self.write_space();
32371        // kind should be UNION, INTERSECT, EXCEPT, etc.
32372        if let Some(kind) = &e.kind {
32373            self.write_keyword(kind);
32374        }
32375        if e.distinct {
32376            self.write_space();
32377            self.write_keyword("DISTINCT");
32378        } else {
32379            self.write_space();
32380            self.write_keyword("ALL");
32381        }
32382        if e.by_name.is_some() {
32383            self.write_space();
32384            self.write_keyword("BY NAME");
32385        }
32386        self.write_space();
32387        self.generate_expression(&e.expression)?;
32388        Ok(())
32389    }
32390
32391    fn generate_set_property(&mut self, e: &SetProperty) -> Result<()> {
32392        // SET or MULTISET
32393        if e.multi.is_some() {
32394            self.write_keyword("MULTISET");
32395        } else {
32396            self.write_keyword("SET");
32397        }
32398        Ok(())
32399    }
32400
32401    fn generate_settings_property(&mut self, e: &SettingsProperty) -> Result<()> {
32402        // SETTINGS expressions
32403        self.write_keyword("SETTINGS");
32404        if self.config.pretty && e.expressions.len() > 1 {
32405            // Pretty print: each setting on its own line, indented
32406            self.indent_level += 1;
32407            for (i, expr) in e.expressions.iter().enumerate() {
32408                if i > 0 {
32409                    self.write(",");
32410                }
32411                self.write_newline();
32412                self.write_indent();
32413                self.generate_expression(expr)?;
32414            }
32415            self.indent_level -= 1;
32416        } else {
32417            self.write_space();
32418            for (i, expr) in e.expressions.iter().enumerate() {
32419                if i > 0 {
32420                    self.write(", ");
32421                }
32422                self.generate_expression(expr)?;
32423            }
32424        }
32425        Ok(())
32426    }
32427
32428    fn generate_sharing_property(&mut self, e: &SharingProperty) -> Result<()> {
32429        // SHARING = this
32430        self.write_keyword("SHARING");
32431        if let Some(this) = &e.this {
32432            self.write(" = ");
32433            self.generate_expression(this)?;
32434        }
32435        Ok(())
32436    }
32437
32438    fn generate_slice(&mut self, e: &Slice) -> Result<()> {
32439        // Python array slicing: begin:end:step
32440        if let Some(begin) = &e.this {
32441            self.generate_expression(begin)?;
32442        }
32443        self.write(":");
32444        if let Some(end) = &e.expression {
32445            self.generate_expression(end)?;
32446        }
32447        if let Some(step) = &e.step {
32448            self.write(":");
32449            self.generate_expression(step)?;
32450        }
32451        Ok(())
32452    }
32453
32454    fn generate_sort_array(&mut self, e: &SortArray) -> Result<()> {
32455        // SORT_ARRAY(this, asc)
32456        self.write_keyword("SORT_ARRAY");
32457        self.write("(");
32458        self.generate_expression(&e.this)?;
32459        if let Some(asc) = &e.asc {
32460            self.write(", ");
32461            self.generate_expression(asc)?;
32462        }
32463        self.write(")");
32464        Ok(())
32465    }
32466
32467    fn generate_sort_by(&mut self, e: &SortBy) -> Result<()> {
32468        // SORT BY expressions
32469        self.write_keyword("SORT BY");
32470        self.write_space();
32471        for (i, expr) in e.expressions.iter().enumerate() {
32472            if i > 0 {
32473                self.write(", ");
32474            }
32475            self.generate_ordered(expr)?;
32476        }
32477        Ok(())
32478    }
32479
32480    fn generate_sort_key_property(&mut self, e: &SortKeyProperty) -> Result<()> {
32481        // [COMPOUND] SORTKEY(col1, col2, ...) - no space before paren
32482        if e.compound.is_some() {
32483            self.write_keyword("COMPOUND");
32484            self.write_space();
32485        }
32486        self.write_keyword("SORTKEY");
32487        self.write("(");
32488        // If this is a Tuple, unwrap its contents to avoid double parentheses
32489        if let Expression::Tuple(t) = e.this.as_ref() {
32490            for (i, expr) in t.expressions.iter().enumerate() {
32491                if i > 0 {
32492                    self.write(", ");
32493                }
32494                self.generate_expression(expr)?;
32495            }
32496        } else {
32497            self.generate_expression(&e.this)?;
32498        }
32499        self.write(")");
32500        Ok(())
32501    }
32502
32503    fn generate_split_part(&mut self, e: &SplitPart) -> Result<()> {
32504        // SPLIT_PART(this, delimiter, part_index)
32505        self.write_keyword("SPLIT_PART");
32506        self.write("(");
32507        self.generate_expression(&e.this)?;
32508        if let Some(delimiter) = &e.delimiter {
32509            self.write(", ");
32510            self.generate_expression(delimiter)?;
32511        }
32512        if let Some(part_index) = &e.part_index {
32513            self.write(", ");
32514            self.generate_expression(part_index)?;
32515        }
32516        self.write(")");
32517        Ok(())
32518    }
32519
32520    fn generate_sql_read_write_property(&mut self, e: &SqlReadWriteProperty) -> Result<()> {
32521        // READS SQL DATA or MODIFIES SQL DATA, etc.
32522        self.generate_expression(&e.this)?;
32523        Ok(())
32524    }
32525
32526    fn generate_sql_security_property(&mut self, e: &SqlSecurityProperty) -> Result<()> {
32527        // SQL SECURITY DEFINER or SQL SECURITY INVOKER
32528        self.write_keyword("SQL SECURITY");
32529        self.write_space();
32530        self.generate_expression(&e.this)?;
32531        Ok(())
32532    }
32533
32534    fn generate_st_distance(&mut self, e: &StDistance) -> Result<()> {
32535        // ST_DISTANCE(this, expression, [use_spheroid])
32536        self.write_keyword("ST_DISTANCE");
32537        self.write("(");
32538        self.generate_expression(&e.this)?;
32539        self.write(", ");
32540        self.generate_expression(&e.expression)?;
32541        if let Some(use_spheroid) = &e.use_spheroid {
32542            self.write(", ");
32543            self.generate_expression(use_spheroid)?;
32544        }
32545        self.write(")");
32546        Ok(())
32547    }
32548
32549    fn generate_st_point(&mut self, e: &StPoint) -> Result<()> {
32550        // ST_POINT(this, expression)
32551        self.write_keyword("ST_POINT");
32552        self.write("(");
32553        self.generate_expression(&e.this)?;
32554        self.write(", ");
32555        self.generate_expression(&e.expression)?;
32556        self.write(")");
32557        Ok(())
32558    }
32559
32560    fn generate_stability_property(&mut self, e: &StabilityProperty) -> Result<()> {
32561        // IMMUTABLE, STABLE, VOLATILE
32562        self.generate_expression(&e.this)?;
32563        Ok(())
32564    }
32565
32566    fn generate_standard_hash(&mut self, e: &StandardHash) -> Result<()> {
32567        // STANDARD_HASH(this, [expression])
32568        self.write_keyword("STANDARD_HASH");
32569        self.write("(");
32570        self.generate_expression(&e.this)?;
32571        if let Some(expression) = &e.expression {
32572            self.write(", ");
32573            self.generate_expression(expression)?;
32574        }
32575        self.write(")");
32576        Ok(())
32577    }
32578
32579    fn generate_storage_handler_property(&mut self, e: &StorageHandlerProperty) -> Result<()> {
32580        // STORED BY this
32581        self.write_keyword("STORED BY");
32582        self.write_space();
32583        self.generate_expression(&e.this)?;
32584        Ok(())
32585    }
32586
32587    fn generate_str_position(&mut self, e: &StrPosition) -> Result<()> {
32588        // STRPOS(this, substr) or STRPOS(this, substr, position)
32589        // Different dialects have different function names
32590        use crate::dialects::DialectType;
32591        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
32592            // Snowflake: CHARINDEX(substr, str[, position])
32593            self.write_keyword("CHARINDEX");
32594            self.write("(");
32595            if let Some(substr) = &e.substr {
32596                self.generate_expression(substr)?;
32597                self.write(", ");
32598            }
32599            self.generate_expression(&e.this)?;
32600            if let Some(position) = &e.position {
32601                self.write(", ");
32602                self.generate_expression(position)?;
32603            }
32604            self.write(")");
32605        } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
32606            self.write_keyword("POSITION");
32607            self.write("(");
32608            self.generate_expression(&e.this)?;
32609            if let Some(substr) = &e.substr {
32610                self.write(", ");
32611                self.generate_expression(substr)?;
32612            }
32613            if let Some(position) = &e.position {
32614                self.write(", ");
32615                self.generate_expression(position)?;
32616            }
32617            if let Some(occurrence) = &e.occurrence {
32618                self.write(", ");
32619                self.generate_expression(occurrence)?;
32620            }
32621            self.write(")");
32622        } else if matches!(
32623            self.config.dialect,
32624            Some(DialectType::SQLite)
32625                | Some(DialectType::Oracle)
32626                | Some(DialectType::BigQuery)
32627                | Some(DialectType::Teradata)
32628        ) {
32629            self.write_keyword("INSTR");
32630            self.write("(");
32631            self.generate_expression(&e.this)?;
32632            if let Some(substr) = &e.substr {
32633                self.write(", ");
32634                self.generate_expression(substr)?;
32635            }
32636            if let Some(position) = &e.position {
32637                self.write(", ");
32638                self.generate_expression(position)?;
32639            } else if e.occurrence.is_some() {
32640                // INSTR requires a position arg before occurrence: INSTR(str, substr, start, nth)
32641                // Default start position is 1
32642                self.write(", 1");
32643            }
32644            if let Some(occurrence) = &e.occurrence {
32645                self.write(", ");
32646                self.generate_expression(occurrence)?;
32647            }
32648            self.write(")");
32649        } else if matches!(
32650            self.config.dialect,
32651            Some(DialectType::MySQL)
32652                | Some(DialectType::SingleStore)
32653                | Some(DialectType::Doris)
32654                | Some(DialectType::StarRocks)
32655                | Some(DialectType::Hive)
32656                | Some(DialectType::Spark)
32657                | Some(DialectType::Databricks)
32658        ) {
32659            // LOCATE(substr, str[, position]) - substr first
32660            self.write_keyword("LOCATE");
32661            self.write("(");
32662            if let Some(substr) = &e.substr {
32663                self.generate_expression(substr)?;
32664                self.write(", ");
32665            }
32666            self.generate_expression(&e.this)?;
32667            if let Some(position) = &e.position {
32668                self.write(", ");
32669                self.generate_expression(position)?;
32670            }
32671            self.write(")");
32672        } else if matches!(self.config.dialect, Some(DialectType::TSQL)) {
32673            // CHARINDEX(substr, str[, position])
32674            self.write_keyword("CHARINDEX");
32675            self.write("(");
32676            if let Some(substr) = &e.substr {
32677                self.generate_expression(substr)?;
32678                self.write(", ");
32679            }
32680            self.generate_expression(&e.this)?;
32681            if let Some(position) = &e.position {
32682                self.write(", ");
32683                self.generate_expression(position)?;
32684            }
32685            self.write(")");
32686        } else if matches!(
32687            self.config.dialect,
32688            Some(DialectType::PostgreSQL)
32689                | Some(DialectType::Materialize)
32690                | Some(DialectType::RisingWave)
32691                | Some(DialectType::Redshift)
32692        ) {
32693            // POSITION(substr IN str) syntax
32694            self.write_keyword("POSITION");
32695            self.write("(");
32696            if let Some(substr) = &e.substr {
32697                self.generate_expression(substr)?;
32698                self.write(" IN ");
32699            }
32700            self.generate_expression(&e.this)?;
32701            self.write(")");
32702        } else {
32703            self.write_keyword("STRPOS");
32704            self.write("(");
32705            self.generate_expression(&e.this)?;
32706            if let Some(substr) = &e.substr {
32707                self.write(", ");
32708                self.generate_expression(substr)?;
32709            }
32710            if let Some(position) = &e.position {
32711                self.write(", ");
32712                self.generate_expression(position)?;
32713            }
32714            if let Some(occurrence) = &e.occurrence {
32715                self.write(", ");
32716                self.generate_expression(occurrence)?;
32717            }
32718            self.write(")");
32719        }
32720        Ok(())
32721    }
32722
32723    fn generate_str_to_date(&mut self, e: &StrToDate) -> Result<()> {
32724        match self.config.dialect {
32725            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
32726                // TO_DATE(this, java_format)
32727                self.write_keyword("TO_DATE");
32728                self.write("(");
32729                self.generate_expression(&e.this)?;
32730                if let Some(format) = &e.format {
32731                    self.write(", '");
32732                    self.write(&Self::strftime_to_java_format(format));
32733                    self.write("'");
32734                }
32735                self.write(")");
32736            }
32737            Some(DialectType::DuckDB) => {
32738                // CAST(STRPTIME(this, format) AS DATE)
32739                self.write_keyword("CAST");
32740                self.write("(");
32741                self.write_keyword("STRPTIME");
32742                self.write("(");
32743                self.generate_expression(&e.this)?;
32744                if let Some(format) = &e.format {
32745                    self.write(", '");
32746                    self.write(format);
32747                    self.write("'");
32748                }
32749                self.write(")");
32750                self.write_keyword(" AS ");
32751                self.write_keyword("DATE");
32752                self.write(")");
32753            }
32754            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
32755                // TO_DATE(this, pg_format)
32756                self.write_keyword("TO_DATE");
32757                self.write("(");
32758                self.generate_expression(&e.this)?;
32759                if let Some(format) = &e.format {
32760                    self.write(", '");
32761                    self.write(&Self::strftime_to_postgres_format(format));
32762                    self.write("'");
32763                }
32764                self.write(")");
32765            }
32766            Some(DialectType::BigQuery) => {
32767                // PARSE_DATE(format, this) - note: format comes first for BigQuery
32768                self.write_keyword("PARSE_DATE");
32769                self.write("(");
32770                if let Some(format) = &e.format {
32771                    self.write("'");
32772                    self.write(format);
32773                    self.write("'");
32774                    self.write(", ");
32775                }
32776                self.generate_expression(&e.this)?;
32777                self.write(")");
32778            }
32779            Some(DialectType::Teradata) => {
32780                // CAST(this AS DATE FORMAT 'teradata_fmt')
32781                self.write_keyword("CAST");
32782                self.write("(");
32783                self.generate_expression(&e.this)?;
32784                self.write_keyword(" AS ");
32785                self.write_keyword("DATE");
32786                if let Some(format) = &e.format {
32787                    self.write_keyword(" FORMAT ");
32788                    self.write("'");
32789                    self.write(&Self::strftime_to_teradata_format(format));
32790                    self.write("'");
32791                }
32792                self.write(")");
32793            }
32794            _ => {
32795                // STR_TO_DATE(this, format) - MySQL default
32796                self.write_keyword("STR_TO_DATE");
32797                self.write("(");
32798                self.generate_expression(&e.this)?;
32799                if let Some(format) = &e.format {
32800                    self.write(", '");
32801                    self.write(format);
32802                    self.write("'");
32803                }
32804                self.write(")");
32805            }
32806        }
32807        Ok(())
32808    }
32809
32810    /// Convert strftime format to Teradata date format (YYYY, DD, MM, etc.)
32811    fn strftime_to_teradata_format(fmt: &str) -> String {
32812        let mut result = fmt.to_string();
32813        result = result.replace("%Y", "YYYY");
32814        result = result.replace("%y", "YY");
32815        result = result.replace("%m", "MM");
32816        result = result.replace("%B", "MMMM");
32817        result = result.replace("%b", "MMM");
32818        result = result.replace("%d", "DD");
32819        result = result.replace("%j", "DDD");
32820        result = result.replace("%H", "HH");
32821        result = result.replace("%M", "MI");
32822        result = result.replace("%S", "SS");
32823        result = result.replace("%f", "SSSSSS");
32824        result = result.replace("%A", "EEEE");
32825        result = result.replace("%a", "EEE");
32826        result
32827    }
32828
32829    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
32830    /// Public static version for use by other modules
32831    pub fn strftime_to_java_format_static(fmt: &str) -> String {
32832        Self::strftime_to_java_format(fmt)
32833    }
32834
32835    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
32836    fn strftime_to_java_format(fmt: &str) -> String {
32837        let mut result = fmt.to_string();
32838        // Handle non-padded variants BEFORE their padded counterparts
32839        result = result.replace("%-d", "d");
32840        result = result.replace("%-m", "M");
32841        result = result.replace("%-H", "H");
32842        result = result.replace("%-M", "m");
32843        result = result.replace("%-S", "s");
32844        result = result.replace("%Y", "yyyy");
32845        result = result.replace("%y", "yy");
32846        result = result.replace("%m", "MM");
32847        result = result.replace("%B", "MMMM");
32848        result = result.replace("%b", "MMM");
32849        result = result.replace("%d", "dd");
32850        result = result.replace("%j", "DDD");
32851        result = result.replace("%H", "HH");
32852        result = result.replace("%M", "mm");
32853        result = result.replace("%S", "ss");
32854        result = result.replace("%f", "SSSSSS");
32855        result = result.replace("%A", "EEEE");
32856        result = result.replace("%a", "EEE");
32857        result
32858    }
32859
32860    /// Convert strftime format (%Y, %m, %d, etc.) to .NET date format for TSQL FORMAT()
32861    /// Similar to Java but uses ffffff for microseconds instead of SSSSSS
32862    fn strftime_to_tsql_format(fmt: &str) -> String {
32863        let mut result = fmt.to_string();
32864        // Handle non-padded variants BEFORE their padded counterparts
32865        result = result.replace("%-d", "d");
32866        result = result.replace("%-m", "M");
32867        result = result.replace("%-H", "H");
32868        result = result.replace("%-M", "m");
32869        result = result.replace("%-S", "s");
32870        result = result.replace("%Y", "yyyy");
32871        result = result.replace("%y", "yy");
32872        result = result.replace("%m", "MM");
32873        result = result.replace("%B", "MMMM");
32874        result = result.replace("%b", "MMM");
32875        result = result.replace("%d", "dd");
32876        result = result.replace("%j", "DDD");
32877        result = result.replace("%H", "HH");
32878        result = result.replace("%M", "mm");
32879        result = result.replace("%S", "ss");
32880        result = result.replace("%f", "ffffff");
32881        result = result.replace("%A", "dddd");
32882        result = result.replace("%a", "ddd");
32883        result
32884    }
32885
32886    /// Decompose a JSON path string like "$.y[0].z" into individual parts: ["y", "0", "z"]
32887    /// This is used for PostgreSQL/Redshift JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT
32888    fn decompose_json_path(path: &str) -> Vec<String> {
32889        let mut parts = Vec::new();
32890        // Strip leading $ and optional .
32891        let path = if path.starts_with("$.") {
32892            &path[2..]
32893        } else if path.starts_with('$') {
32894            &path[1..]
32895        } else {
32896            path
32897        };
32898        if path.is_empty() {
32899            return parts;
32900        }
32901        let mut current = String::new();
32902        let chars: Vec<char> = path.chars().collect();
32903        let mut i = 0;
32904        while i < chars.len() {
32905            match chars[i] {
32906                '.' => {
32907                    if !current.is_empty() {
32908                        parts.push(current.clone());
32909                        current.clear();
32910                    }
32911                    i += 1;
32912                }
32913                '[' => {
32914                    if !current.is_empty() {
32915                        parts.push(current.clone());
32916                        current.clear();
32917                    }
32918                    i += 1;
32919                    // Read the content inside brackets
32920                    let mut bracket_content = String::new();
32921                    while i < chars.len() && chars[i] != ']' {
32922                        // Skip quotes inside brackets
32923                        if chars[i] == '"' || chars[i] == '\'' {
32924                            let quote = chars[i];
32925                            i += 1;
32926                            while i < chars.len() && chars[i] != quote {
32927                                bracket_content.push(chars[i]);
32928                                i += 1;
32929                            }
32930                            if i < chars.len() {
32931                                i += 1;
32932                            } // skip closing quote
32933                        } else {
32934                            bracket_content.push(chars[i]);
32935                            i += 1;
32936                        }
32937                    }
32938                    if i < chars.len() {
32939                        i += 1;
32940                    } // skip ]
32941                      // Skip wildcard [*] - don't add as a part
32942                    if bracket_content != "*" {
32943                        parts.push(bracket_content);
32944                    }
32945                }
32946                _ => {
32947                    current.push(chars[i]);
32948                    i += 1;
32949                }
32950            }
32951        }
32952        if !current.is_empty() {
32953            parts.push(current);
32954        }
32955        parts
32956    }
32957
32958    /// Convert strftime format to PostgreSQL date format (YYYY, MM, DD, etc.)
32959    fn strftime_to_postgres_format(fmt: &str) -> String {
32960        let mut result = fmt.to_string();
32961        // Handle non-padded variants BEFORE their padded counterparts
32962        result = result.replace("%-d", "FMDD");
32963        result = result.replace("%-m", "FMMM");
32964        result = result.replace("%-H", "FMHH24");
32965        result = result.replace("%-M", "FMMI");
32966        result = result.replace("%-S", "FMSS");
32967        result = result.replace("%Y", "YYYY");
32968        result = result.replace("%y", "YY");
32969        result = result.replace("%m", "MM");
32970        result = result.replace("%B", "Month");
32971        result = result.replace("%b", "Mon");
32972        result = result.replace("%d", "DD");
32973        result = result.replace("%j", "DDD");
32974        result = result.replace("%H", "HH24");
32975        result = result.replace("%M", "MI");
32976        result = result.replace("%S", "SS");
32977        result = result.replace("%f", "US");
32978        result = result.replace("%A", "Day");
32979        result = result.replace("%a", "Dy");
32980        result
32981    }
32982
32983    /// Convert strftime format to Snowflake date format (yyyy, mm, DD, etc.)
32984    fn strftime_to_snowflake_format(fmt: &str) -> String {
32985        let mut result = fmt.to_string();
32986        // Handle %-d (non-padded day) before %d (padded day)
32987        result = result.replace("%-d", "dd");
32988        result = result.replace("%-m", "mm"); // non-padded month
32989        result = result.replace("%Y", "yyyy");
32990        result = result.replace("%y", "yy");
32991        result = result.replace("%m", "mm");
32992        result = result.replace("%d", "DD");
32993        result = result.replace("%H", "hh24");
32994        result = result.replace("%M", "mi");
32995        result = result.replace("%S", "ss");
32996        result = result.replace("%f", "ff");
32997        result
32998    }
32999
33000    fn generate_str_to_map(&mut self, e: &StrToMap) -> Result<()> {
33001        // STR_TO_MAP(this, pair_delim, key_value_delim)
33002        self.write_keyword("STR_TO_MAP");
33003        self.write("(");
33004        self.generate_expression(&e.this)?;
33005        // Spark/Hive: STR_TO_MAP needs explicit default delimiters
33006        let needs_defaults = matches!(
33007            self.config.dialect,
33008            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
33009        );
33010        if let Some(pair_delim) = &e.pair_delim {
33011            self.write(", ");
33012            self.generate_expression(pair_delim)?;
33013        } else if needs_defaults {
33014            self.write(", ','");
33015        }
33016        if let Some(key_value_delim) = &e.key_value_delim {
33017            self.write(", ");
33018            self.generate_expression(key_value_delim)?;
33019        } else if needs_defaults {
33020            self.write(", ':'");
33021        }
33022        self.write(")");
33023        Ok(())
33024    }
33025
33026    fn generate_str_to_time(&mut self, e: &StrToTime) -> Result<()> {
33027        // Detect format style: strftime (starts with %) vs Snowflake/Java
33028        let is_strftime = e.format.contains('%');
33029        // Helper: get strftime format from whatever style is stored
33030        let to_strftime = |f: &str| -> String {
33031            if is_strftime {
33032                f.to_string()
33033            } else {
33034                Self::snowflake_format_to_strftime(f)
33035            }
33036        };
33037        // Helper: get Java format
33038        let to_java = |f: &str| -> String {
33039            if is_strftime {
33040                Self::strftime_to_java_format(f)
33041            } else {
33042                Self::snowflake_format_to_spark(f)
33043            }
33044        };
33045        // Helper: get PG format
33046        let to_pg = |f: &str| -> String {
33047            if is_strftime {
33048                Self::strftime_to_postgres_format(f)
33049            } else {
33050                Self::convert_strptime_to_postgres_format(f)
33051            }
33052        };
33053
33054        match self.config.dialect {
33055            Some(DialectType::Exasol) => {
33056                self.write_keyword("TO_DATE");
33057                self.write("(");
33058                self.generate_expression(&e.this)?;
33059                self.write(", '");
33060                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
33061                self.write("'");
33062                self.write(")");
33063            }
33064            Some(DialectType::BigQuery) => {
33065                // BigQuery: PARSE_TIMESTAMP(format, value) - note swapped args
33066                let fmt = to_strftime(&e.format);
33067                // BigQuery normalizes: %Y-%m-%d -> %F, %H:%M:%S -> %T
33068                let fmt = fmt.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
33069                self.write_keyword("PARSE_TIMESTAMP");
33070                self.write("('");
33071                self.write(&fmt);
33072                self.write("', ");
33073                self.generate_expression(&e.this)?;
33074                self.write(")");
33075            }
33076            Some(DialectType::Hive) => {
33077                // Hive: CAST(x AS TIMESTAMP) for simple date formats
33078                // Check both the raw format and the converted format (in case it's already Java)
33079                let java_fmt = to_java(&e.format);
33080                if java_fmt == "yyyy-MM-dd HH:mm:ss"
33081                    || java_fmt == "yyyy-MM-dd"
33082                    || e.format == "yyyy-MM-dd HH:mm:ss"
33083                    || e.format == "yyyy-MM-dd"
33084                {
33085                    self.write_keyword("CAST");
33086                    self.write("(");
33087                    self.generate_expression(&e.this)?;
33088                    self.write(" ");
33089                    self.write_keyword("AS TIMESTAMP");
33090                    self.write(")");
33091                } else {
33092                    // CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, java_fmt)) AS TIMESTAMP)
33093                    self.write_keyword("CAST");
33094                    self.write("(");
33095                    self.write_keyword("FROM_UNIXTIME");
33096                    self.write("(");
33097                    self.write_keyword("UNIX_TIMESTAMP");
33098                    self.write("(");
33099                    self.generate_expression(&e.this)?;
33100                    self.write(", '");
33101                    self.write(&java_fmt);
33102                    self.write("')");
33103                    self.write(") ");
33104                    self.write_keyword("AS TIMESTAMP");
33105                    self.write(")");
33106                }
33107            }
33108            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
33109                // Spark: TO_TIMESTAMP(value, java_format)
33110                let java_fmt = to_java(&e.format);
33111                self.write_keyword("TO_TIMESTAMP");
33112                self.write("(");
33113                self.generate_expression(&e.this)?;
33114                self.write(", '");
33115                self.write(&java_fmt);
33116                self.write("')");
33117            }
33118            Some(DialectType::MySQL) => {
33119                // MySQL: STR_TO_DATE(value, format)
33120                let mut fmt = to_strftime(&e.format);
33121                // MySQL uses %e for non-padded day, %T for %H:%M:%S
33122                fmt = fmt.replace("%-d", "%e");
33123                fmt = fmt.replace("%-m", "%c");
33124                fmt = fmt.replace("%H:%M:%S", "%T");
33125                self.write_keyword("STR_TO_DATE");
33126                self.write("(");
33127                self.generate_expression(&e.this)?;
33128                self.write(", '");
33129                self.write(&fmt);
33130                self.write("')");
33131            }
33132            Some(DialectType::Drill) => {
33133                // Drill: TO_TIMESTAMP(value, java_format) with T quoted in single quotes
33134                let java_fmt = to_java(&e.format);
33135                // Drill quotes literal T character: T -> ''T'' (double-quoted within SQL string literal)
33136                let java_fmt = java_fmt.replace('T', "''T''");
33137                self.write_keyword("TO_TIMESTAMP");
33138                self.write("(");
33139                self.generate_expression(&e.this)?;
33140                self.write(", '");
33141                self.write(&java_fmt);
33142                self.write("')");
33143            }
33144            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
33145                // Presto: DATE_PARSE(value, strftime_format)
33146                let mut fmt = to_strftime(&e.format);
33147                // Presto uses %e for non-padded day, %T for %H:%M:%S
33148                fmt = fmt.replace("%-d", "%e");
33149                fmt = fmt.replace("%-m", "%c");
33150                fmt = fmt.replace("%H:%M:%S", "%T");
33151                self.write_keyword("DATE_PARSE");
33152                self.write("(");
33153                self.generate_expression(&e.this)?;
33154                self.write(", '");
33155                self.write(&fmt);
33156                self.write("')");
33157            }
33158            Some(DialectType::DuckDB) => {
33159                // DuckDB: STRPTIME(value, strftime_format)
33160                let fmt = to_strftime(&e.format);
33161                self.write_keyword("STRPTIME");
33162                self.write("(");
33163                self.generate_expression(&e.this)?;
33164                self.write(", '");
33165                self.write(&fmt);
33166                self.write("')");
33167            }
33168            Some(DialectType::PostgreSQL)
33169            | Some(DialectType::Redshift)
33170            | Some(DialectType::Materialize) => {
33171                // PostgreSQL/Redshift/Materialize: TO_TIMESTAMP(value, pg_format)
33172                let pg_fmt = to_pg(&e.format);
33173                self.write_keyword("TO_TIMESTAMP");
33174                self.write("(");
33175                self.generate_expression(&e.this)?;
33176                self.write(", '");
33177                self.write(&pg_fmt);
33178                self.write("')");
33179            }
33180            Some(DialectType::Oracle) => {
33181                // Oracle: TO_TIMESTAMP(value, pg_format)
33182                let pg_fmt = to_pg(&e.format);
33183                self.write_keyword("TO_TIMESTAMP");
33184                self.write("(");
33185                self.generate_expression(&e.this)?;
33186                self.write(", '");
33187                self.write(&pg_fmt);
33188                self.write("')");
33189            }
33190            Some(DialectType::Snowflake) => {
33191                // Snowflake: TO_TIMESTAMP(value, format) - native format
33192                self.write_keyword("TO_TIMESTAMP");
33193                self.write("(");
33194                self.generate_expression(&e.this)?;
33195                self.write(", '");
33196                self.write(&e.format);
33197                self.write("')");
33198            }
33199            _ => {
33200                // Default: STR_TO_TIME(this, format)
33201                self.write_keyword("STR_TO_TIME");
33202                self.write("(");
33203                self.generate_expression(&e.this)?;
33204                self.write(", '");
33205                self.write(&e.format);
33206                self.write("'");
33207                self.write(")");
33208            }
33209        }
33210        Ok(())
33211    }
33212
33213    /// Convert Snowflake normalized format to strftime-style (%Y, %m, etc.)
33214    fn snowflake_format_to_strftime(format: &str) -> String {
33215        let mut result = String::new();
33216        let chars: Vec<char> = format.chars().collect();
33217        let mut i = 0;
33218        while i < chars.len() {
33219            let remaining = &format[i..];
33220            if remaining.starts_with("yyyy") {
33221                result.push_str("%Y");
33222                i += 4;
33223            } else if remaining.starts_with("yy") {
33224                result.push_str("%y");
33225                i += 2;
33226            } else if remaining.starts_with("mmmm") {
33227                result.push_str("%B"); // full month name
33228                i += 4;
33229            } else if remaining.starts_with("mon") {
33230                result.push_str("%b"); // abbreviated month
33231                i += 3;
33232            } else if remaining.starts_with("mm") {
33233                result.push_str("%m");
33234                i += 2;
33235            } else if remaining.starts_with("DD") {
33236                result.push_str("%d");
33237                i += 2;
33238            } else if remaining.starts_with("dy") {
33239                result.push_str("%a"); // abbreviated day name
33240                i += 2;
33241            } else if remaining.starts_with("hh24") {
33242                result.push_str("%H");
33243                i += 4;
33244            } else if remaining.starts_with("hh12") {
33245                result.push_str("%I");
33246                i += 4;
33247            } else if remaining.starts_with("hh") {
33248                result.push_str("%H");
33249                i += 2;
33250            } else if remaining.starts_with("mi") {
33251                result.push_str("%M");
33252                i += 2;
33253            } else if remaining.starts_with("ss") {
33254                result.push_str("%S");
33255                i += 2;
33256            } else if remaining.starts_with("ff") {
33257                // Fractional seconds
33258                result.push_str("%f");
33259                i += 2;
33260                // Skip digits after ff (ff3, ff6, ff9)
33261                while i < chars.len() && chars[i].is_ascii_digit() {
33262                    i += 1;
33263                }
33264            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
33265                result.push_str("%p");
33266                i += 2;
33267            } else if remaining.starts_with("tz") {
33268                result.push_str("%Z");
33269                i += 2;
33270            } else {
33271                result.push(chars[i]);
33272                i += 1;
33273            }
33274        }
33275        result
33276    }
33277
33278    /// Convert Snowflake normalized format to Spark format (Java-style)
33279    fn snowflake_format_to_spark(format: &str) -> String {
33280        let mut result = String::new();
33281        let chars: Vec<char> = format.chars().collect();
33282        let mut i = 0;
33283        while i < chars.len() {
33284            let remaining = &format[i..];
33285            if remaining.starts_with("yyyy") {
33286                result.push_str("yyyy");
33287                i += 4;
33288            } else if remaining.starts_with("yy") {
33289                result.push_str("yy");
33290                i += 2;
33291            } else if remaining.starts_with("mmmm") {
33292                result.push_str("MMMM"); // full month name
33293                i += 4;
33294            } else if remaining.starts_with("mon") {
33295                result.push_str("MMM"); // abbreviated month
33296                i += 3;
33297            } else if remaining.starts_with("mm") {
33298                result.push_str("MM");
33299                i += 2;
33300            } else if remaining.starts_with("DD") {
33301                result.push_str("dd");
33302                i += 2;
33303            } else if remaining.starts_with("dy") {
33304                result.push_str("EEE"); // abbreviated day name
33305                i += 2;
33306            } else if remaining.starts_with("hh24") {
33307                result.push_str("HH");
33308                i += 4;
33309            } else if remaining.starts_with("hh12") {
33310                result.push_str("hh");
33311                i += 4;
33312            } else if remaining.starts_with("hh") {
33313                result.push_str("HH");
33314                i += 2;
33315            } else if remaining.starts_with("mi") {
33316                result.push_str("mm");
33317                i += 2;
33318            } else if remaining.starts_with("ss") {
33319                result.push_str("ss");
33320                i += 2;
33321            } else if remaining.starts_with("ff") {
33322                result.push_str("SSS"); // milliseconds
33323                i += 2;
33324                // Skip digits after ff
33325                while i < chars.len() && chars[i].is_ascii_digit() {
33326                    i += 1;
33327                }
33328            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
33329                result.push_str("a");
33330                i += 2;
33331            } else if remaining.starts_with("tz") {
33332                result.push_str("z");
33333                i += 2;
33334            } else {
33335                result.push(chars[i]);
33336                i += 1;
33337            }
33338        }
33339        result
33340    }
33341
33342    fn generate_str_to_unix(&mut self, e: &StrToUnix) -> Result<()> {
33343        match self.config.dialect {
33344            Some(DialectType::DuckDB) => {
33345                // DuckDB: EPOCH(STRPTIME(value, format))
33346                self.write_keyword("EPOCH");
33347                self.write("(");
33348                self.write_keyword("STRPTIME");
33349                self.write("(");
33350                if let Some(this) = &e.this {
33351                    self.generate_expression(this)?;
33352                }
33353                if let Some(format) = &e.format {
33354                    self.write(", '");
33355                    self.write(format);
33356                    self.write("'");
33357                }
33358                self.write("))");
33359            }
33360            Some(DialectType::Hive) => {
33361                // Hive: UNIX_TIMESTAMP(value, java_format) - convert C fmt to Java
33362                self.write_keyword("UNIX_TIMESTAMP");
33363                self.write("(");
33364                if let Some(this) = &e.this {
33365                    self.generate_expression(this)?;
33366                }
33367                if let Some(format) = &e.format {
33368                    let java_fmt = Self::strftime_to_java_format(format);
33369                    if java_fmt != "yyyy-MM-dd HH:mm:ss" {
33370                        self.write(", '");
33371                        self.write(&java_fmt);
33372                        self.write("'");
33373                    }
33374                }
33375                self.write(")");
33376            }
33377            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
33378                // Doris/StarRocks: UNIX_TIMESTAMP(value, format) - C format
33379                self.write_keyword("UNIX_TIMESTAMP");
33380                self.write("(");
33381                if let Some(this) = &e.this {
33382                    self.generate_expression(this)?;
33383                }
33384                if let Some(format) = &e.format {
33385                    self.write(", '");
33386                    self.write(format);
33387                    self.write("'");
33388                }
33389                self.write(")");
33390            }
33391            Some(DialectType::Presto) | Some(DialectType::Trino) => {
33392                // Presto: TO_UNIXTIME(COALESCE(TRY(DATE_PARSE(CAST(value AS VARCHAR), c_format)),
33393                //   PARSE_DATETIME(DATE_FORMAT(CAST(value AS TIMESTAMP), c_format), java_format)))
33394                let c_fmt = e.format.as_deref().unwrap_or("%Y-%m-%d %T");
33395                let java_fmt = Self::strftime_to_java_format(c_fmt);
33396                self.write_keyword("TO_UNIXTIME");
33397                self.write("(");
33398                self.write_keyword("COALESCE");
33399                self.write("(");
33400                self.write_keyword("TRY");
33401                self.write("(");
33402                self.write_keyword("DATE_PARSE");
33403                self.write("(");
33404                self.write_keyword("CAST");
33405                self.write("(");
33406                if let Some(this) = &e.this {
33407                    self.generate_expression(this)?;
33408                }
33409                self.write(" ");
33410                self.write_keyword("AS VARCHAR");
33411                self.write("), '");
33412                self.write(c_fmt);
33413                self.write("')), ");
33414                self.write_keyword("PARSE_DATETIME");
33415                self.write("(");
33416                self.write_keyword("DATE_FORMAT");
33417                self.write("(");
33418                self.write_keyword("CAST");
33419                self.write("(");
33420                if let Some(this) = &e.this {
33421                    self.generate_expression(this)?;
33422                }
33423                self.write(" ");
33424                self.write_keyword("AS TIMESTAMP");
33425                self.write("), '");
33426                self.write(c_fmt);
33427                self.write("'), '");
33428                self.write(&java_fmt);
33429                self.write("')))");
33430            }
33431            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
33432                // Spark: UNIX_TIMESTAMP(value, java_format)
33433                self.write_keyword("UNIX_TIMESTAMP");
33434                self.write("(");
33435                if let Some(this) = &e.this {
33436                    self.generate_expression(this)?;
33437                }
33438                if let Some(format) = &e.format {
33439                    let java_fmt = Self::strftime_to_java_format(format);
33440                    self.write(", '");
33441                    self.write(&java_fmt);
33442                    self.write("'");
33443                }
33444                self.write(")");
33445            }
33446            _ => {
33447                // Default: STR_TO_UNIX(this, format)
33448                self.write_keyword("STR_TO_UNIX");
33449                self.write("(");
33450                if let Some(this) = &e.this {
33451                    self.generate_expression(this)?;
33452                }
33453                if let Some(format) = &e.format {
33454                    self.write(", '");
33455                    self.write(format);
33456                    self.write("'");
33457                }
33458                self.write(")");
33459            }
33460        }
33461        Ok(())
33462    }
33463
33464    fn generate_string_to_array(&mut self, e: &StringToArray) -> Result<()> {
33465        // STRING_TO_ARRAY(this, delimiter, null_string)
33466        self.write_keyword("STRING_TO_ARRAY");
33467        self.write("(");
33468        self.generate_expression(&e.this)?;
33469        if let Some(expression) = &e.expression {
33470            self.write(", ");
33471            self.generate_expression(expression)?;
33472        }
33473        if let Some(null_val) = &e.null {
33474            self.write(", ");
33475            self.generate_expression(null_val)?;
33476        }
33477        self.write(")");
33478        Ok(())
33479    }
33480
33481    fn generate_struct(&mut self, e: &Struct) -> Result<()> {
33482        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
33483            // Snowflake: OBJECT_CONSTRUCT('key', value, 'key', value, ...)
33484            self.write_keyword("OBJECT_CONSTRUCT");
33485            self.write("(");
33486            for (i, (name, expr)) in e.fields.iter().enumerate() {
33487                if i > 0 {
33488                    self.write(", ");
33489                }
33490                if let Some(name) = name {
33491                    self.write("'");
33492                    self.write(name);
33493                    self.write("'");
33494                    self.write(", ");
33495                } else {
33496                    self.write("'_");
33497                    self.write(&i.to_string());
33498                    self.write("'");
33499                    self.write(", ");
33500                }
33501                self.generate_expression(expr)?;
33502            }
33503            self.write(")");
33504        } else if self.config.struct_curly_brace_notation {
33505            // DuckDB-style: {'key': value, ...}
33506            self.write("{");
33507            for (i, (name, expr)) in e.fields.iter().enumerate() {
33508                if i > 0 {
33509                    self.write(", ");
33510                }
33511                if let Some(name) = name {
33512                    // Quote the key as a string literal
33513                    self.write("'");
33514                    self.write(name);
33515                    self.write("'");
33516                    self.write(": ");
33517                } else {
33518                    // Unnamed field: use positional key
33519                    self.write("'_");
33520                    self.write(&i.to_string());
33521                    self.write("'");
33522                    self.write(": ");
33523                }
33524                self.generate_expression(expr)?;
33525            }
33526            self.write("}");
33527        } else {
33528            // Standard SQL struct notation
33529            // BigQuery/Spark/Databricks use: STRUCT(value AS name, ...)
33530            // Others (Presto etc.) use: STRUCT(name AS value, ...) or ROW(value, ...)
33531            let value_as_name = matches!(
33532                self.config.dialect,
33533                Some(DialectType::BigQuery)
33534                    | Some(DialectType::Spark)
33535                    | Some(DialectType::Databricks)
33536                    | Some(DialectType::Hive)
33537            );
33538            self.write_keyword("STRUCT");
33539            self.write("(");
33540            for (i, (name, expr)) in e.fields.iter().enumerate() {
33541                if i > 0 {
33542                    self.write(", ");
33543                }
33544                if let Some(name) = name {
33545                    if value_as_name {
33546                        // STRUCT(value AS name)
33547                        self.generate_expression(expr)?;
33548                        self.write_space();
33549                        self.write_keyword("AS");
33550                        self.write_space();
33551                        // Quote name if it contains spaces or special chars
33552                        let needs_quoting = name.contains(' ') || name.contains('-');
33553                        if needs_quoting {
33554                            if matches!(
33555                                self.config.dialect,
33556                                Some(DialectType::Spark)
33557                                    | Some(DialectType::Databricks)
33558                                    | Some(DialectType::Hive)
33559                            ) {
33560                                self.write("`");
33561                                self.write(name);
33562                                self.write("`");
33563                            } else {
33564                                self.write(name);
33565                            }
33566                        } else {
33567                            self.write(name);
33568                        }
33569                    } else {
33570                        // STRUCT(name AS value)
33571                        self.write(name);
33572                        self.write_space();
33573                        self.write_keyword("AS");
33574                        self.write_space();
33575                        self.generate_expression(expr)?;
33576                    }
33577                } else {
33578                    self.generate_expression(expr)?;
33579                }
33580            }
33581            self.write(")");
33582        }
33583        Ok(())
33584    }
33585
33586    fn generate_stuff(&mut self, e: &Stuff) -> Result<()> {
33587        // STUFF(this, start, length, expression)
33588        self.write_keyword("STUFF");
33589        self.write("(");
33590        self.generate_expression(&e.this)?;
33591        if let Some(start) = &e.start {
33592            self.write(", ");
33593            self.generate_expression(start)?;
33594        }
33595        if let Some(length) = e.length {
33596            self.write(", ");
33597            self.write(&length.to_string());
33598        }
33599        self.write(", ");
33600        self.generate_expression(&e.expression)?;
33601        self.write(")");
33602        Ok(())
33603    }
33604
33605    fn generate_substring_index(&mut self, e: &SubstringIndex) -> Result<()> {
33606        // SUBSTRING_INDEX(this, delimiter, count)
33607        self.write_keyword("SUBSTRING_INDEX");
33608        self.write("(");
33609        self.generate_expression(&e.this)?;
33610        if let Some(delimiter) = &e.delimiter {
33611            self.write(", ");
33612            self.generate_expression(delimiter)?;
33613        }
33614        if let Some(count) = &e.count {
33615            self.write(", ");
33616            self.generate_expression(count)?;
33617        }
33618        self.write(")");
33619        Ok(())
33620    }
33621
33622    fn generate_summarize(&mut self, e: &Summarize) -> Result<()> {
33623        // SUMMARIZE [TABLE] this
33624        self.write_keyword("SUMMARIZE");
33625        if e.table.is_some() {
33626            self.write_space();
33627            self.write_keyword("TABLE");
33628        }
33629        self.write_space();
33630        self.generate_expression(&e.this)?;
33631        Ok(())
33632    }
33633
33634    fn generate_systimestamp(&mut self, _e: &Systimestamp) -> Result<()> {
33635        // SYSTIMESTAMP
33636        self.write_keyword("SYSTIMESTAMP");
33637        Ok(())
33638    }
33639
33640    fn generate_table_alias(&mut self, e: &TableAlias) -> Result<()> {
33641        // alias (columns...)
33642        if let Some(this) = &e.this {
33643            self.generate_expression(this)?;
33644        }
33645        if !e.columns.is_empty() {
33646            self.write("(");
33647            for (i, col) in e.columns.iter().enumerate() {
33648                if i > 0 {
33649                    self.write(", ");
33650                }
33651                self.generate_expression(col)?;
33652            }
33653            self.write(")");
33654        }
33655        Ok(())
33656    }
33657
33658    fn generate_table_from_rows(&mut self, e: &TableFromRows) -> Result<()> {
33659        // TABLE(this) [AS alias]
33660        self.write_keyword("TABLE");
33661        self.write("(");
33662        self.generate_expression(&e.this)?;
33663        self.write(")");
33664        if let Some(alias) = &e.alias {
33665            self.write_space();
33666            self.write_keyword("AS");
33667            self.write_space();
33668            self.write(alias);
33669        }
33670        Ok(())
33671    }
33672
33673    fn generate_rows_from(&mut self, e: &RowsFrom) -> Result<()> {
33674        // ROWS FROM (func1(...) AS alias1(...), func2(...) AS alias2(...)) [WITH ORDINALITY] [AS alias(...)]
33675        self.write_keyword("ROWS FROM");
33676        self.write(" (");
33677        for (i, expr) in e.expressions.iter().enumerate() {
33678            if i > 0 {
33679                self.write(", ");
33680            }
33681            // Each expression is either:
33682            // - A plain function (no alias)
33683            // - A Tuple(function, TableAlias) for: FUNC() AS alias(col type, ...)
33684            match expr {
33685                Expression::Tuple(tuple) if tuple.expressions.len() == 2 => {
33686                    // First element is the function, second is the TableAlias
33687                    self.generate_expression(&tuple.expressions[0])?;
33688                    self.write_space();
33689                    self.write_keyword("AS");
33690                    self.write_space();
33691                    self.generate_expression(&tuple.expressions[1])?;
33692                }
33693                _ => {
33694                    self.generate_expression(expr)?;
33695                }
33696            }
33697        }
33698        self.write(")");
33699        if e.ordinality {
33700            self.write_space();
33701            self.write_keyword("WITH ORDINALITY");
33702        }
33703        if let Some(alias) = &e.alias {
33704            self.write_space();
33705            self.write_keyword("AS");
33706            self.write_space();
33707            self.generate_expression(alias)?;
33708        }
33709        Ok(())
33710    }
33711
33712    fn generate_table_sample(&mut self, e: &TableSample) -> Result<()> {
33713        use crate::dialects::DialectType;
33714
33715        // New wrapper pattern: expression + Sample struct
33716        if let (Some(this), Some(sample)) = (&e.this, &e.sample) {
33717            // For alias_post_tablesample dialects (Spark, Hive, Oracle): output base expr, TABLESAMPLE, then alias
33718            if self.config.alias_post_tablesample {
33719                // Handle Subquery with alias and Alias wrapper
33720                if let Expression::Subquery(ref s) = **this {
33721                    if let Some(ref alias) = s.alias {
33722                        // Create a clone without alias for output
33723                        let mut subquery_no_alias = (**s).clone();
33724                        subquery_no_alias.alias = None;
33725                        subquery_no_alias.column_aliases = Vec::new();
33726                        self.generate_expression(&Expression::Subquery(Box::new(
33727                            subquery_no_alias,
33728                        )))?;
33729                        self.write_space();
33730                        self.write_keyword("TABLESAMPLE");
33731                        self.generate_sample_body(sample)?;
33732                        if let Some(ref seed) = sample.seed {
33733                            self.write_space();
33734                            let use_seed = sample.use_seed_keyword
33735                                && !matches!(
33736                                    self.config.dialect,
33737                                    Some(crate::dialects::DialectType::Databricks)
33738                                        | Some(crate::dialects::DialectType::Spark)
33739                                );
33740                            if use_seed {
33741                                self.write_keyword("SEED");
33742                            } else {
33743                                self.write_keyword("REPEATABLE");
33744                            }
33745                            self.write(" (");
33746                            self.generate_expression(seed)?;
33747                            self.write(")");
33748                        }
33749                        self.write_space();
33750                        self.write_keyword("AS");
33751                        self.write_space();
33752                        self.generate_identifier(alias)?;
33753                        return Ok(());
33754                    }
33755                } else if let Expression::Alias(ref a) = **this {
33756                    // Output the base expression without alias
33757                    self.generate_expression(&a.this)?;
33758                    self.write_space();
33759                    self.write_keyword("TABLESAMPLE");
33760                    self.generate_sample_body(sample)?;
33761                    if let Some(ref seed) = sample.seed {
33762                        self.write_space();
33763                        let use_seed = sample.use_seed_keyword
33764                            && !matches!(
33765                                self.config.dialect,
33766                                Some(crate::dialects::DialectType::Databricks)
33767                                    | Some(crate::dialects::DialectType::Spark)
33768                            );
33769                        if use_seed {
33770                            self.write_keyword("SEED");
33771                        } else {
33772                            self.write_keyword("REPEATABLE");
33773                        }
33774                        self.write(" (");
33775                        self.generate_expression(seed)?;
33776                        self.write(")");
33777                    }
33778                    // Output alias after TABLESAMPLE
33779                    self.write_space();
33780                    self.write_keyword("AS");
33781                    self.write_space();
33782                    self.generate_identifier(&a.alias)?;
33783                    return Ok(());
33784                }
33785            }
33786            // Default: generate wrapped expression first, then TABLESAMPLE
33787            self.generate_expression(this)?;
33788            self.write_space();
33789            self.write_keyword("TABLESAMPLE");
33790            self.generate_sample_body(sample)?;
33791            // Seed for table-level sample
33792            if let Some(ref seed) = sample.seed {
33793                self.write_space();
33794                // Databricks uses REPEATABLE, not SEED
33795                let use_seed = sample.use_seed_keyword
33796                    && !matches!(
33797                        self.config.dialect,
33798                        Some(crate::dialects::DialectType::Databricks)
33799                            | Some(crate::dialects::DialectType::Spark)
33800                    );
33801                if use_seed {
33802                    self.write_keyword("SEED");
33803                } else {
33804                    self.write_keyword("REPEATABLE");
33805                }
33806                self.write(" (");
33807                self.generate_expression(seed)?;
33808                self.write(")");
33809            }
33810            return Ok(());
33811        }
33812
33813        // Legacy pattern: TABLESAMPLE [method] (expressions) or TABLESAMPLE method BUCKET numerator OUT OF denominator
33814        self.write_keyword("TABLESAMPLE");
33815        if let Some(method) = &e.method {
33816            self.write_space();
33817            self.write_keyword(method);
33818        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
33819            // Snowflake defaults to BERNOULLI when no method is specified
33820            self.write_space();
33821            self.write_keyword("BERNOULLI");
33822        }
33823        if let (Some(numerator), Some(denominator)) = (&e.bucket_numerator, &e.bucket_denominator) {
33824            self.write_space();
33825            self.write_keyword("BUCKET");
33826            self.write_space();
33827            self.generate_expression(numerator)?;
33828            self.write_space();
33829            self.write_keyword("OUT OF");
33830            self.write_space();
33831            self.generate_expression(denominator)?;
33832            if let Some(field) = &e.bucket_field {
33833                self.write_space();
33834                self.write_keyword("ON");
33835                self.write_space();
33836                self.generate_expression(field)?;
33837            }
33838        } else if !e.expressions.is_empty() {
33839            self.write(" (");
33840            for (i, expr) in e.expressions.iter().enumerate() {
33841                if i > 0 {
33842                    self.write(", ");
33843                }
33844                self.generate_expression(expr)?;
33845            }
33846            self.write(")");
33847        } else if let Some(percent) = &e.percent {
33848            self.write(" (");
33849            self.generate_expression(percent)?;
33850            self.write_space();
33851            self.write_keyword("PERCENT");
33852            self.write(")");
33853        }
33854        Ok(())
33855    }
33856
33857    fn generate_tag(&mut self, e: &Tag) -> Result<()> {
33858        // [prefix]this[postfix]
33859        if let Some(prefix) = &e.prefix {
33860            self.generate_expression(prefix)?;
33861        }
33862        if let Some(this) = &e.this {
33863            self.generate_expression(this)?;
33864        }
33865        if let Some(postfix) = &e.postfix {
33866            self.generate_expression(postfix)?;
33867        }
33868        Ok(())
33869    }
33870
33871    fn generate_tags(&mut self, e: &Tags) -> Result<()> {
33872        // TAG (expressions)
33873        self.write_keyword("TAG");
33874        self.write(" (");
33875        for (i, expr) in e.expressions.iter().enumerate() {
33876            if i > 0 {
33877                self.write(", ");
33878            }
33879            self.generate_expression(expr)?;
33880        }
33881        self.write(")");
33882        Ok(())
33883    }
33884
33885    fn generate_temporary_property(&mut self, e: &TemporaryProperty) -> Result<()> {
33886        // TEMPORARY or TEMP or [this] TEMPORARY
33887        if let Some(this) = &e.this {
33888            self.generate_expression(this)?;
33889            self.write_space();
33890        }
33891        self.write_keyword("TEMPORARY");
33892        Ok(())
33893    }
33894
33895    /// Generate a Time function expression
33896    /// For most dialects: TIME('value')
33897    fn generate_time_func(&mut self, e: &UnaryFunc) -> Result<()> {
33898        // Standard: TIME(value)
33899        self.write_keyword("TIME");
33900        self.write("(");
33901        self.generate_expression(&e.this)?;
33902        self.write(")");
33903        Ok(())
33904    }
33905
33906    fn generate_time_add(&mut self, e: &TimeAdd) -> Result<()> {
33907        // TIME_ADD(this, expression, unit)
33908        self.write_keyword("TIME_ADD");
33909        self.write("(");
33910        self.generate_expression(&e.this)?;
33911        self.write(", ");
33912        self.generate_expression(&e.expression)?;
33913        if let Some(unit) = &e.unit {
33914            self.write(", ");
33915            self.write_keyword(unit);
33916        }
33917        self.write(")");
33918        Ok(())
33919    }
33920
33921    fn generate_time_diff(&mut self, e: &TimeDiff) -> Result<()> {
33922        // TIME_DIFF(this, expression, unit)
33923        self.write_keyword("TIME_DIFF");
33924        self.write("(");
33925        self.generate_expression(&e.this)?;
33926        self.write(", ");
33927        self.generate_expression(&e.expression)?;
33928        if let Some(unit) = &e.unit {
33929            self.write(", ");
33930            self.write_keyword(unit);
33931        }
33932        self.write(")");
33933        Ok(())
33934    }
33935
33936    fn generate_time_from_parts(&mut self, e: &TimeFromParts) -> Result<()> {
33937        // TIME_FROM_PARTS(hour, minute, second, nanosecond)
33938        self.write_keyword("TIME_FROM_PARTS");
33939        self.write("(");
33940        let mut first = true;
33941        if let Some(hour) = &e.hour {
33942            self.generate_expression(hour)?;
33943            first = false;
33944        }
33945        if let Some(minute) = &e.min {
33946            if !first {
33947                self.write(", ");
33948            }
33949            self.generate_expression(minute)?;
33950            first = false;
33951        }
33952        if let Some(second) = &e.sec {
33953            if !first {
33954                self.write(", ");
33955            }
33956            self.generate_expression(second)?;
33957            first = false;
33958        }
33959        if let Some(ns) = &e.nano {
33960            if !first {
33961                self.write(", ");
33962            }
33963            self.generate_expression(ns)?;
33964        }
33965        self.write(")");
33966        Ok(())
33967    }
33968
33969    fn generate_time_slice(&mut self, e: &TimeSlice) -> Result<()> {
33970        // TIME_SLICE(this, expression, unit)
33971        self.write_keyword("TIME_SLICE");
33972        self.write("(");
33973        self.generate_expression(&e.this)?;
33974        self.write(", ");
33975        self.generate_expression(&e.expression)?;
33976        self.write(", ");
33977        self.write_keyword(&e.unit);
33978        self.write(")");
33979        Ok(())
33980    }
33981
33982    fn generate_time_str_to_time(&mut self, e: &TimeStrToTime) -> Result<()> {
33983        // TIME_STR_TO_TIME(this)
33984        self.write_keyword("TIME_STR_TO_TIME");
33985        self.write("(");
33986        self.generate_expression(&e.this)?;
33987        self.write(")");
33988        Ok(())
33989    }
33990
33991    fn generate_time_sub(&mut self, e: &TimeSub) -> Result<()> {
33992        // TIME_SUB(this, expression, unit)
33993        self.write_keyword("TIME_SUB");
33994        self.write("(");
33995        self.generate_expression(&e.this)?;
33996        self.write(", ");
33997        self.generate_expression(&e.expression)?;
33998        if let Some(unit) = &e.unit {
33999            self.write(", ");
34000            self.write_keyword(unit);
34001        }
34002        self.write(")");
34003        Ok(())
34004    }
34005
34006    fn generate_time_to_str(&mut self, e: &TimeToStr) -> Result<()> {
34007        match self.config.dialect {
34008            Some(DialectType::Exasol) => {
34009                // Exasol uses TO_CHAR with Exasol-specific format
34010                self.write_keyword("TO_CHAR");
34011                self.write("(");
34012                self.generate_expression(&e.this)?;
34013                self.write(", '");
34014                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
34015                self.write("'");
34016                self.write(")");
34017            }
34018            Some(DialectType::PostgreSQL)
34019            | Some(DialectType::Redshift)
34020            | Some(DialectType::Materialize) => {
34021                // PostgreSQL/Redshift/Materialize uses TO_CHAR with PG-specific format
34022                self.write_keyword("TO_CHAR");
34023                self.write("(");
34024                self.generate_expression(&e.this)?;
34025                self.write(", '");
34026                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
34027                self.write("'");
34028                self.write(")");
34029            }
34030            Some(DialectType::Oracle) => {
34031                // Oracle uses TO_CHAR with PG-like format
34032                self.write_keyword("TO_CHAR");
34033                self.write("(");
34034                self.generate_expression(&e.this)?;
34035                self.write(", '");
34036                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
34037                self.write("'");
34038                self.write(")");
34039            }
34040            Some(DialectType::Drill) => {
34041                // Drill: TO_CHAR with Java format
34042                self.write_keyword("TO_CHAR");
34043                self.write("(");
34044                self.generate_expression(&e.this)?;
34045                self.write(", '");
34046                self.write(&Self::strftime_to_java_format(&e.format));
34047                self.write("'");
34048                self.write(")");
34049            }
34050            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
34051                // TSQL: FORMAT(value, format) with .NET-style format
34052                self.write_keyword("FORMAT");
34053                self.write("(");
34054                self.generate_expression(&e.this)?;
34055                self.write(", '");
34056                self.write(&Self::strftime_to_tsql_format(&e.format));
34057                self.write("'");
34058                self.write(")");
34059            }
34060            Some(DialectType::DuckDB) => {
34061                // DuckDB: STRFTIME(value, format) - keeps C format
34062                self.write_keyword("STRFTIME");
34063                self.write("(");
34064                self.generate_expression(&e.this)?;
34065                self.write(", '");
34066                self.write(&e.format);
34067                self.write("'");
34068                self.write(")");
34069            }
34070            Some(DialectType::BigQuery) => {
34071                // BigQuery: FORMAT_DATE(format, value) - note swapped arg order
34072                // Normalize: %Y-%m-%d -> %F, %H:%M:%S -> %T
34073                let fmt = e.format.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
34074                self.write_keyword("FORMAT_DATE");
34075                self.write("('");
34076                self.write(&fmt);
34077                self.write("', ");
34078                self.generate_expression(&e.this)?;
34079                self.write(")");
34080            }
34081            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
34082                // Hive/Spark: DATE_FORMAT(value, java_format)
34083                self.write_keyword("DATE_FORMAT");
34084                self.write("(");
34085                self.generate_expression(&e.this)?;
34086                self.write(", '");
34087                self.write(&Self::strftime_to_java_format(&e.format));
34088                self.write("'");
34089                self.write(")");
34090            }
34091            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
34092                // Presto/Trino: DATE_FORMAT(value, format) - keeps C format
34093                self.write_keyword("DATE_FORMAT");
34094                self.write("(");
34095                self.generate_expression(&e.this)?;
34096                self.write(", '");
34097                self.write(&e.format);
34098                self.write("'");
34099                self.write(")");
34100            }
34101            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
34102                // Doris/StarRocks: DATE_FORMAT(value, format) - keeps C format
34103                self.write_keyword("DATE_FORMAT");
34104                self.write("(");
34105                self.generate_expression(&e.this)?;
34106                self.write(", '");
34107                self.write(&e.format);
34108                self.write("'");
34109                self.write(")");
34110            }
34111            _ => {
34112                // Default: TIME_TO_STR(this, format)
34113                self.write_keyword("TIME_TO_STR");
34114                self.write("(");
34115                self.generate_expression(&e.this)?;
34116                self.write(", '");
34117                self.write(&e.format);
34118                self.write("'");
34119                self.write(")");
34120            }
34121        }
34122        Ok(())
34123    }
34124
34125    fn generate_time_to_unix(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
34126        match self.config.dialect {
34127            Some(DialectType::DuckDB) => {
34128                // DuckDB: EPOCH(x)
34129                self.write_keyword("EPOCH");
34130                self.write("(");
34131                self.generate_expression(&e.this)?;
34132                self.write(")");
34133            }
34134            Some(DialectType::Hive)
34135            | Some(DialectType::Spark)
34136            | Some(DialectType::Databricks)
34137            | Some(DialectType::Doris)
34138            | Some(DialectType::StarRocks)
34139            | Some(DialectType::Drill) => {
34140                // Hive/Spark/Doris/StarRocks/Drill: UNIX_TIMESTAMP(x)
34141                self.write_keyword("UNIX_TIMESTAMP");
34142                self.write("(");
34143                self.generate_expression(&e.this)?;
34144                self.write(")");
34145            }
34146            Some(DialectType::Presto) | Some(DialectType::Trino) => {
34147                // Presto: TO_UNIXTIME(x)
34148                self.write_keyword("TO_UNIXTIME");
34149                self.write("(");
34150                self.generate_expression(&e.this)?;
34151                self.write(")");
34152            }
34153            _ => {
34154                // Default: TIME_TO_UNIX(x)
34155                self.write_keyword("TIME_TO_UNIX");
34156                self.write("(");
34157                self.generate_expression(&e.this)?;
34158                self.write(")");
34159            }
34160        }
34161        Ok(())
34162    }
34163
34164    fn generate_time_str_to_date(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
34165        match self.config.dialect {
34166            Some(DialectType::Hive) => {
34167                // Hive: TO_DATE(x)
34168                self.write_keyword("TO_DATE");
34169                self.write("(");
34170                self.generate_expression(&e.this)?;
34171                self.write(")");
34172            }
34173            _ => {
34174                // Default: TIME_STR_TO_DATE(x)
34175                self.write_keyword("TIME_STR_TO_DATE");
34176                self.write("(");
34177                self.generate_expression(&e.this)?;
34178                self.write(")");
34179            }
34180        }
34181        Ok(())
34182    }
34183
34184    fn generate_time_trunc(&mut self, e: &TimeTrunc) -> Result<()> {
34185        // TIME_TRUNC(this, unit)
34186        self.write_keyword("TIME_TRUNC");
34187        self.write("(");
34188        self.generate_expression(&e.this)?;
34189        self.write(", ");
34190        self.write_keyword(&e.unit);
34191        self.write(")");
34192        Ok(())
34193    }
34194
34195    fn generate_time_unit(&mut self, e: &TimeUnit) -> Result<()> {
34196        // Just output the unit name
34197        if let Some(unit) = &e.unit {
34198            self.write_keyword(unit);
34199        }
34200        Ok(())
34201    }
34202
34203    /// Generate a Timestamp function expression
34204    /// For Exasol: {ts'value'} -> TO_TIMESTAMP('value')
34205    /// For other dialects: TIMESTAMP('value')
34206    fn generate_timestamp_func(&mut self, e: &TimestampFunc) -> Result<()> {
34207        use crate::dialects::DialectType;
34208        use crate::expressions::Literal;
34209
34210        match self.config.dialect {
34211            // Exasol uses TO_TIMESTAMP for Timestamp expressions
34212            Some(DialectType::Exasol) => {
34213                self.write_keyword("TO_TIMESTAMP");
34214                self.write("(");
34215                // Extract the string value from the expression if it's a string literal
34216                if let Some(this) = &e.this {
34217                    match this.as_ref() {
34218                        Expression::Literal(Literal::String(s)) => {
34219                            self.write("'");
34220                            self.write(s);
34221                            self.write("'");
34222                        }
34223                        _ => {
34224                            self.generate_expression(this)?;
34225                        }
34226                    }
34227                }
34228                self.write(")");
34229            }
34230            // Standard: TIMESTAMP(value) or TIMESTAMP(value, zone)
34231            _ => {
34232                self.write_keyword("TIMESTAMP");
34233                self.write("(");
34234                if let Some(this) = &e.this {
34235                    self.generate_expression(this)?;
34236                }
34237                if let Some(zone) = &e.zone {
34238                    self.write(", ");
34239                    self.generate_expression(zone)?;
34240                }
34241                self.write(")");
34242            }
34243        }
34244        Ok(())
34245    }
34246
34247    fn generate_timestamp_add(&mut self, e: &TimestampAdd) -> Result<()> {
34248        // TIMESTAMP_ADD(this, expression, unit)
34249        self.write_keyword("TIMESTAMP_ADD");
34250        self.write("(");
34251        self.generate_expression(&e.this)?;
34252        self.write(", ");
34253        self.generate_expression(&e.expression)?;
34254        if let Some(unit) = &e.unit {
34255            self.write(", ");
34256            self.write_keyword(unit);
34257        }
34258        self.write(")");
34259        Ok(())
34260    }
34261
34262    fn generate_timestamp_diff(&mut self, e: &TimestampDiff) -> Result<()> {
34263        // TIMESTAMP_DIFF(this, expression, unit)
34264        self.write_keyword("TIMESTAMP_DIFF");
34265        self.write("(");
34266        self.generate_expression(&e.this)?;
34267        self.write(", ");
34268        self.generate_expression(&e.expression)?;
34269        if let Some(unit) = &e.unit {
34270            self.write(", ");
34271            self.write_keyword(unit);
34272        }
34273        self.write(")");
34274        Ok(())
34275    }
34276
34277    fn generate_timestamp_from_parts(&mut self, e: &TimestampFromParts) -> Result<()> {
34278        // TIMESTAMP_FROM_PARTS(this, expression)
34279        self.write_keyword("TIMESTAMP_FROM_PARTS");
34280        self.write("(");
34281        if let Some(this) = &e.this {
34282            self.generate_expression(this)?;
34283        }
34284        if let Some(expression) = &e.expression {
34285            self.write(", ");
34286            self.generate_expression(expression)?;
34287        }
34288        if let Some(zone) = &e.zone {
34289            self.write(", ");
34290            self.generate_expression(zone)?;
34291        }
34292        if let Some(milli) = &e.milli {
34293            self.write(", ");
34294            self.generate_expression(milli)?;
34295        }
34296        self.write(")");
34297        Ok(())
34298    }
34299
34300    fn generate_timestamp_sub(&mut self, e: &TimestampSub) -> Result<()> {
34301        // TIMESTAMP_SUB(this, INTERVAL expression unit)
34302        self.write_keyword("TIMESTAMP_SUB");
34303        self.write("(");
34304        self.generate_expression(&e.this)?;
34305        self.write(", ");
34306        self.write_keyword("INTERVAL");
34307        self.write_space();
34308        self.generate_expression(&e.expression)?;
34309        if let Some(unit) = &e.unit {
34310            self.write_space();
34311            self.write_keyword(unit);
34312        }
34313        self.write(")");
34314        Ok(())
34315    }
34316
34317    fn generate_timestamp_tz_from_parts(&mut self, e: &TimestampTzFromParts) -> Result<()> {
34318        // TIMESTAMP_TZ_FROM_PARTS(...)
34319        self.write_keyword("TIMESTAMP_TZ_FROM_PARTS");
34320        self.write("(");
34321        if let Some(zone) = &e.zone {
34322            self.generate_expression(zone)?;
34323        }
34324        self.write(")");
34325        Ok(())
34326    }
34327
34328    fn generate_to_binary(&mut self, e: &ToBinary) -> Result<()> {
34329        // TO_BINARY(this, [format])
34330        self.write_keyword("TO_BINARY");
34331        self.write("(");
34332        self.generate_expression(&e.this)?;
34333        if let Some(format) = &e.format {
34334            self.write(", '");
34335            self.write(format);
34336            self.write("'");
34337        }
34338        self.write(")");
34339        Ok(())
34340    }
34341
34342    fn generate_to_boolean(&mut self, e: &ToBoolean) -> Result<()> {
34343        // TO_BOOLEAN(this)
34344        self.write_keyword("TO_BOOLEAN");
34345        self.write("(");
34346        self.generate_expression(&e.this)?;
34347        self.write(")");
34348        Ok(())
34349    }
34350
34351    fn generate_to_char(&mut self, e: &ToChar) -> Result<()> {
34352        // TO_CHAR(this, [format], [nlsparam])
34353        self.write_keyword("TO_CHAR");
34354        self.write("(");
34355        self.generate_expression(&e.this)?;
34356        if let Some(format) = &e.format {
34357            self.write(", '");
34358            self.write(format);
34359            self.write("'");
34360        }
34361        if let Some(nlsparam) = &e.nlsparam {
34362            self.write(", ");
34363            self.generate_expression(nlsparam)?;
34364        }
34365        self.write(")");
34366        Ok(())
34367    }
34368
34369    fn generate_to_decfloat(&mut self, e: &ToDecfloat) -> Result<()> {
34370        // TO_DECFLOAT(this, [format])
34371        self.write_keyword("TO_DECFLOAT");
34372        self.write("(");
34373        self.generate_expression(&e.this)?;
34374        if let Some(format) = &e.format {
34375            self.write(", '");
34376            self.write(format);
34377            self.write("'");
34378        }
34379        self.write(")");
34380        Ok(())
34381    }
34382
34383    fn generate_to_double(&mut self, e: &ToDouble) -> Result<()> {
34384        // TO_DOUBLE(this, [format])
34385        self.write_keyword("TO_DOUBLE");
34386        self.write("(");
34387        self.generate_expression(&e.this)?;
34388        if let Some(format) = &e.format {
34389            self.write(", '");
34390            self.write(format);
34391            self.write("'");
34392        }
34393        self.write(")");
34394        Ok(())
34395    }
34396
34397    fn generate_to_file(&mut self, e: &ToFile) -> Result<()> {
34398        // TO_FILE(this, path)
34399        self.write_keyword("TO_FILE");
34400        self.write("(");
34401        self.generate_expression(&e.this)?;
34402        if let Some(path) = &e.path {
34403            self.write(", ");
34404            self.generate_expression(path)?;
34405        }
34406        self.write(")");
34407        Ok(())
34408    }
34409
34410    fn generate_to_number(&mut self, e: &ToNumber) -> Result<()> {
34411        // TO_NUMBER or TRY_TO_NUMBER (this, [format], [precision], [scale])
34412        // If safe flag is set, output TRY_TO_NUMBER
34413        let is_safe = e.safe.is_some();
34414        if is_safe {
34415            self.write_keyword("TRY_TO_NUMBER");
34416        } else {
34417            self.write_keyword("TO_NUMBER");
34418        }
34419        self.write("(");
34420        self.generate_expression(&e.this)?;
34421        if let Some(format) = &e.format {
34422            self.write(", ");
34423            self.generate_expression(format)?;
34424        }
34425        if let Some(nlsparam) = &e.nlsparam {
34426            self.write(", ");
34427            self.generate_expression(nlsparam)?;
34428        }
34429        if let Some(precision) = &e.precision {
34430            self.write(", ");
34431            self.generate_expression(precision)?;
34432        }
34433        if let Some(scale) = &e.scale {
34434            self.write(", ");
34435            self.generate_expression(scale)?;
34436        }
34437        self.write(")");
34438        Ok(())
34439    }
34440
34441    fn generate_to_table_property(&mut self, e: &ToTableProperty) -> Result<()> {
34442        // TO_TABLE this
34443        self.write_keyword("TO_TABLE");
34444        self.write_space();
34445        self.generate_expression(&e.this)?;
34446        Ok(())
34447    }
34448
34449    fn generate_transaction(&mut self, e: &Transaction) -> Result<()> {
34450        // Check mark to determine the format
34451        let mark_text = e.mark.as_ref().map(|m| match m.as_ref() {
34452            Expression::Identifier(id) => id.name.clone(),
34453            Expression::Literal(Literal::String(s)) => s.clone(),
34454            _ => String::new(),
34455        });
34456
34457        let is_start = mark_text.as_ref().map_or(false, |s| s == "START");
34458        let has_transaction_keyword = mark_text.as_ref().map_or(false, |s| s == "TRANSACTION");
34459        let has_with_mark = e.mark.as_ref().map_or(false, |m| {
34460            matches!(m.as_ref(), Expression::Literal(Literal::String(_)))
34461        });
34462
34463        // For Presto/Trino: always use START TRANSACTION
34464        let use_start_transaction = matches!(
34465            self.config.dialect,
34466            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
34467        );
34468        // For most dialects: strip TRANSACTION keyword
34469        let strip_transaction = matches!(
34470            self.config.dialect,
34471            Some(DialectType::Snowflake)
34472                | Some(DialectType::PostgreSQL)
34473                | Some(DialectType::Redshift)
34474                | Some(DialectType::MySQL)
34475                | Some(DialectType::Hive)
34476                | Some(DialectType::Spark)
34477                | Some(DialectType::Databricks)
34478                | Some(DialectType::DuckDB)
34479                | Some(DialectType::Oracle)
34480                | Some(DialectType::Doris)
34481                | Some(DialectType::StarRocks)
34482                | Some(DialectType::Materialize)
34483                | Some(DialectType::ClickHouse)
34484        );
34485
34486        if is_start || use_start_transaction {
34487            // START TRANSACTION [modes]
34488            self.write_keyword("START TRANSACTION");
34489            if let Some(modes) = &e.modes {
34490                self.write_space();
34491                self.generate_expression(modes)?;
34492            }
34493        } else {
34494            // BEGIN [DEFERRED|IMMEDIATE|EXCLUSIVE] [TRANSACTION] [transaction_name] [WITH MARK 'desc']
34495            self.write_keyword("BEGIN");
34496
34497            // Check if `this` is a transaction kind (DEFERRED/IMMEDIATE/EXCLUSIVE)
34498            let is_kind = e.this.as_ref().map_or(false, |t| {
34499                if let Expression::Identifier(id) = t.as_ref() {
34500                    matches!(
34501                        id.name.to_uppercase().as_str(),
34502                        "DEFERRED" | "IMMEDIATE" | "EXCLUSIVE"
34503                    )
34504                } else {
34505                    false
34506                }
34507            });
34508
34509            // Output kind before TRANSACTION keyword
34510            if is_kind {
34511                if let Some(this) = &e.this {
34512                    self.write_space();
34513                    if let Expression::Identifier(id) = this.as_ref() {
34514                        self.write_keyword(&id.name);
34515                    }
34516                }
34517            }
34518
34519            // Output TRANSACTION keyword if it was present and target supports it
34520            if (has_transaction_keyword || has_with_mark) && !strip_transaction {
34521                self.write_space();
34522                self.write_keyword("TRANSACTION");
34523            }
34524
34525            // Output transaction name (not kind)
34526            if !is_kind {
34527                if let Some(this) = &e.this {
34528                    self.write_space();
34529                    self.generate_expression(this)?;
34530                }
34531            }
34532
34533            // Output WITH MARK 'description' for TSQL
34534            if has_with_mark {
34535                self.write_space();
34536                self.write_keyword("WITH MARK");
34537                if let Some(Expression::Literal(Literal::String(desc))) = e.mark.as_deref() {
34538                    if !desc.is_empty() {
34539                        self.write_space();
34540                        self.write(&format!("'{}'", desc));
34541                    }
34542                }
34543            }
34544
34545            // Output modes (isolation levels, etc.)
34546            if let Some(modes) = &e.modes {
34547                self.write_space();
34548                self.generate_expression(modes)?;
34549            }
34550        }
34551        Ok(())
34552    }
34553
34554    fn generate_transform(&mut self, e: &Transform) -> Result<()> {
34555        // TRANSFORM(this, expression)
34556        self.write_keyword("TRANSFORM");
34557        self.write("(");
34558        self.generate_expression(&e.this)?;
34559        self.write(", ");
34560        self.generate_expression(&e.expression)?;
34561        self.write(")");
34562        Ok(())
34563    }
34564
34565    fn generate_transform_model_property(&mut self, e: &TransformModelProperty) -> Result<()> {
34566        // TRANSFORM(expressions)
34567        self.write_keyword("TRANSFORM");
34568        self.write("(");
34569        if self.config.pretty && !e.expressions.is_empty() {
34570            self.indent_level += 1;
34571            for (i, expr) in e.expressions.iter().enumerate() {
34572                if i > 0 {
34573                    self.write(",");
34574                }
34575                self.write_newline();
34576                self.write_indent();
34577                self.generate_expression(expr)?;
34578            }
34579            self.indent_level -= 1;
34580            self.write_newline();
34581            self.write(")");
34582        } else {
34583            for (i, expr) in e.expressions.iter().enumerate() {
34584                if i > 0 {
34585                    self.write(", ");
34586                }
34587                self.generate_expression(expr)?;
34588            }
34589            self.write(")");
34590        }
34591        Ok(())
34592    }
34593
34594    fn generate_transient_property(&mut self, e: &TransientProperty) -> Result<()> {
34595        use crate::dialects::DialectType;
34596        // TRANSIENT is Snowflake-specific; skip for other dialects
34597        if let Some(this) = &e.this {
34598            self.generate_expression(this)?;
34599            if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
34600                self.write_space();
34601            }
34602        }
34603        if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
34604            self.write_keyword("TRANSIENT");
34605        }
34606        Ok(())
34607    }
34608
34609    fn generate_translate(&mut self, e: &Translate) -> Result<()> {
34610        // TRANSLATE(this, from_, to)
34611        self.write_keyword("TRANSLATE");
34612        self.write("(");
34613        self.generate_expression(&e.this)?;
34614        if let Some(from) = &e.from_ {
34615            self.write(", ");
34616            self.generate_expression(from)?;
34617        }
34618        if let Some(to) = &e.to {
34619            self.write(", ");
34620            self.generate_expression(to)?;
34621        }
34622        self.write(")");
34623        Ok(())
34624    }
34625
34626    fn generate_translate_characters(&mut self, e: &TranslateCharacters) -> Result<()> {
34627        // TRANSLATE(this USING expression)
34628        self.write_keyword("TRANSLATE");
34629        self.write("(");
34630        self.generate_expression(&e.this)?;
34631        self.write_space();
34632        self.write_keyword("USING");
34633        self.write_space();
34634        self.generate_expression(&e.expression)?;
34635        if e.with_error.is_some() {
34636            self.write_space();
34637            self.write_keyword("WITH ERROR");
34638        }
34639        self.write(")");
34640        Ok(())
34641    }
34642
34643    fn generate_truncate_table(&mut self, e: &TruncateTable) -> Result<()> {
34644        // TRUNCATE TABLE table1, table2, ...
34645        self.write_keyword("TRUNCATE TABLE");
34646        self.write_space();
34647        for (i, expr) in e.expressions.iter().enumerate() {
34648            if i > 0 {
34649                self.write(", ");
34650            }
34651            self.generate_expression(expr)?;
34652        }
34653        Ok(())
34654    }
34655
34656    fn generate_try_base64_decode_binary(&mut self, e: &TryBase64DecodeBinary) -> Result<()> {
34657        // TRY_BASE64_DECODE_BINARY(this, [alphabet])
34658        self.write_keyword("TRY_BASE64_DECODE_BINARY");
34659        self.write("(");
34660        self.generate_expression(&e.this)?;
34661        if let Some(alphabet) = &e.alphabet {
34662            self.write(", ");
34663            self.generate_expression(alphabet)?;
34664        }
34665        self.write(")");
34666        Ok(())
34667    }
34668
34669    fn generate_try_base64_decode_string(&mut self, e: &TryBase64DecodeString) -> Result<()> {
34670        // TRY_BASE64_DECODE_STRING(this, [alphabet])
34671        self.write_keyword("TRY_BASE64_DECODE_STRING");
34672        self.write("(");
34673        self.generate_expression(&e.this)?;
34674        if let Some(alphabet) = &e.alphabet {
34675            self.write(", ");
34676            self.generate_expression(alphabet)?;
34677        }
34678        self.write(")");
34679        Ok(())
34680    }
34681
34682    fn generate_try_to_decfloat(&mut self, e: &TryToDecfloat) -> Result<()> {
34683        // TRY_TO_DECFLOAT(this, [format])
34684        self.write_keyword("TRY_TO_DECFLOAT");
34685        self.write("(");
34686        self.generate_expression(&e.this)?;
34687        if let Some(format) = &e.format {
34688            self.write(", '");
34689            self.write(format);
34690            self.write("'");
34691        }
34692        self.write(")");
34693        Ok(())
34694    }
34695
34696    fn generate_ts_or_ds_add(&mut self, e: &TsOrDsAdd) -> Result<()> {
34697        // TS_OR_DS_ADD(this, expression, [unit], [return_type])
34698        self.write_keyword("TS_OR_DS_ADD");
34699        self.write("(");
34700        self.generate_expression(&e.this)?;
34701        self.write(", ");
34702        self.generate_expression(&e.expression)?;
34703        if let Some(unit) = &e.unit {
34704            self.write(", ");
34705            self.write_keyword(unit);
34706        }
34707        if let Some(return_type) = &e.return_type {
34708            self.write(", ");
34709            self.generate_expression(return_type)?;
34710        }
34711        self.write(")");
34712        Ok(())
34713    }
34714
34715    fn generate_ts_or_ds_diff(&mut self, e: &TsOrDsDiff) -> Result<()> {
34716        // TS_OR_DS_DIFF(this, expression, [unit])
34717        self.write_keyword("TS_OR_DS_DIFF");
34718        self.write("(");
34719        self.generate_expression(&e.this)?;
34720        self.write(", ");
34721        self.generate_expression(&e.expression)?;
34722        if let Some(unit) = &e.unit {
34723            self.write(", ");
34724            self.write_keyword(unit);
34725        }
34726        self.write(")");
34727        Ok(())
34728    }
34729
34730    fn generate_ts_or_ds_to_date(&mut self, e: &TsOrDsToDate) -> Result<()> {
34731        let default_time_format = "%Y-%m-%d %H:%M:%S";
34732        let default_date_format = "%Y-%m-%d";
34733        let has_non_default_format = e.format.as_ref().map_or(false, |f| {
34734            f != default_time_format && f != default_date_format
34735        });
34736
34737        if has_non_default_format {
34738            // With non-default format: dialect-specific handling
34739            let fmt = e.format.as_ref().unwrap();
34740            match self.config.dialect {
34741                Some(DialectType::MySQL) | Some(DialectType::StarRocks) => {
34742                    // MySQL/StarRocks: STR_TO_DATE(x, fmt) - no CAST wrapper
34743                    // STR_TO_DATE is the MySQL-native form of StrToTime
34744                    let str_to_time = crate::expressions::StrToTime {
34745                        this: Box::new((*e.this).clone()),
34746                        format: fmt.clone(),
34747                        zone: None,
34748                        safe: None,
34749                        target_type: None,
34750                    };
34751                    self.generate_str_to_time(&str_to_time)?;
34752                }
34753                Some(DialectType::Hive)
34754                | Some(DialectType::Spark)
34755                | Some(DialectType::Databricks) => {
34756                    // Hive/Spark: TO_DATE(x, java_fmt)
34757                    self.write_keyword("TO_DATE");
34758                    self.write("(");
34759                    self.generate_expression(&e.this)?;
34760                    self.write(", '");
34761                    self.write(&Self::strftime_to_java_format(fmt));
34762                    self.write("')");
34763                }
34764                Some(DialectType::Snowflake) => {
34765                    // Snowflake: TO_DATE(x, snowflake_fmt)
34766                    self.write_keyword("TO_DATE");
34767                    self.write("(");
34768                    self.generate_expression(&e.this)?;
34769                    self.write(", '");
34770                    self.write(&Self::strftime_to_snowflake_format(fmt));
34771                    self.write("')");
34772                }
34773                Some(DialectType::Doris) => {
34774                    // Doris: TO_DATE(x) - ignores format
34775                    self.write_keyword("TO_DATE");
34776                    self.write("(");
34777                    self.generate_expression(&e.this)?;
34778                    self.write(")");
34779                }
34780                _ => {
34781                    // Default: CAST(STR_TO_TIME(x, fmt) AS DATE)
34782                    self.write_keyword("CAST");
34783                    self.write("(");
34784                    let str_to_time = crate::expressions::StrToTime {
34785                        this: Box::new((*e.this).clone()),
34786                        format: fmt.clone(),
34787                        zone: None,
34788                        safe: None,
34789                        target_type: None,
34790                    };
34791                    self.generate_str_to_time(&str_to_time)?;
34792                    self.write_keyword(" AS ");
34793                    self.write_keyword("DATE");
34794                    self.write(")");
34795                }
34796            }
34797        } else {
34798            // Without format (or default format): simple date conversion
34799            match self.config.dialect {
34800                Some(DialectType::MySQL)
34801                | Some(DialectType::SQLite)
34802                | Some(DialectType::StarRocks) => {
34803                    // MySQL/SQLite/StarRocks: DATE(x)
34804                    self.write_keyword("DATE");
34805                    self.write("(");
34806                    self.generate_expression(&e.this)?;
34807                    self.write(")");
34808                }
34809                Some(DialectType::Hive)
34810                | Some(DialectType::Spark)
34811                | Some(DialectType::Databricks)
34812                | Some(DialectType::Snowflake)
34813                | Some(DialectType::Doris) => {
34814                    // Hive/Spark/Databricks/Snowflake/Doris: TO_DATE(x)
34815                    self.write_keyword("TO_DATE");
34816                    self.write("(");
34817                    self.generate_expression(&e.this)?;
34818                    self.write(")");
34819                }
34820                Some(DialectType::Presto)
34821                | Some(DialectType::Trino)
34822                | Some(DialectType::Athena) => {
34823                    // Presto/Trino: CAST(CAST(x AS TIMESTAMP) AS DATE)
34824                    self.write_keyword("CAST");
34825                    self.write("(");
34826                    self.write_keyword("CAST");
34827                    self.write("(");
34828                    self.generate_expression(&e.this)?;
34829                    self.write_keyword(" AS ");
34830                    self.write_keyword("TIMESTAMP");
34831                    self.write(")");
34832                    self.write_keyword(" AS ");
34833                    self.write_keyword("DATE");
34834                    self.write(")");
34835                }
34836                Some(DialectType::ClickHouse) => {
34837                    // ClickHouse: CAST(x AS Nullable(DATE))
34838                    self.write_keyword("CAST");
34839                    self.write("(");
34840                    self.generate_expression(&e.this)?;
34841                    self.write_keyword(" AS ");
34842                    self.write("Nullable(DATE)");
34843                    self.write(")");
34844                }
34845                _ => {
34846                    // Default: CAST(x AS DATE)
34847                    self.write_keyword("CAST");
34848                    self.write("(");
34849                    self.generate_expression(&e.this)?;
34850                    self.write_keyword(" AS ");
34851                    self.write_keyword("DATE");
34852                    self.write(")");
34853                }
34854            }
34855        }
34856        Ok(())
34857    }
34858
34859    fn generate_ts_or_ds_to_time(&mut self, e: &TsOrDsToTime) -> Result<()> {
34860        // TS_OR_DS_TO_TIME(this, [format])
34861        self.write_keyword("TS_OR_DS_TO_TIME");
34862        self.write("(");
34863        self.generate_expression(&e.this)?;
34864        if let Some(format) = &e.format {
34865            self.write(", '");
34866            self.write(format);
34867            self.write("'");
34868        }
34869        self.write(")");
34870        Ok(())
34871    }
34872
34873    fn generate_unhex(&mut self, e: &Unhex) -> Result<()> {
34874        // UNHEX(this, [expression])
34875        self.write_keyword("UNHEX");
34876        self.write("(");
34877        self.generate_expression(&e.this)?;
34878        if let Some(expression) = &e.expression {
34879            self.write(", ");
34880            self.generate_expression(expression)?;
34881        }
34882        self.write(")");
34883        Ok(())
34884    }
34885
34886    fn generate_unicode_string(&mut self, e: &UnicodeString) -> Result<()> {
34887        // U&this [UESCAPE escape]
34888        self.write("U&");
34889        self.generate_expression(&e.this)?;
34890        if let Some(escape) = &e.escape {
34891            self.write_space();
34892            self.write_keyword("UESCAPE");
34893            self.write_space();
34894            self.generate_expression(escape)?;
34895        }
34896        Ok(())
34897    }
34898
34899    fn generate_uniform(&mut self, e: &Uniform) -> Result<()> {
34900        // UNIFORM(this, expression, [gen], [seed])
34901        self.write_keyword("UNIFORM");
34902        self.write("(");
34903        self.generate_expression(&e.this)?;
34904        self.write(", ");
34905        self.generate_expression(&e.expression)?;
34906        if let Some(gen) = &e.gen {
34907            self.write(", ");
34908            self.generate_expression(gen)?;
34909        }
34910        if let Some(seed) = &e.seed {
34911            self.write(", ");
34912            self.generate_expression(seed)?;
34913        }
34914        self.write(")");
34915        Ok(())
34916    }
34917
34918    fn generate_unique_column_constraint(&mut self, e: &UniqueColumnConstraint) -> Result<()> {
34919        // UNIQUE [NULLS NOT DISTINCT] [this] [index_type] [on_conflict] [options]
34920        self.write_keyword("UNIQUE");
34921        // Output NULLS NOT DISTINCT if nulls is set (PostgreSQL 15+ feature)
34922        if e.nulls.is_some() {
34923            self.write(" NULLS NOT DISTINCT");
34924        }
34925        if let Some(this) = &e.this {
34926            self.write_space();
34927            self.generate_expression(this)?;
34928        }
34929        if let Some(index_type) = &e.index_type {
34930            self.write(" USING ");
34931            self.generate_expression(index_type)?;
34932        }
34933        if let Some(on_conflict) = &e.on_conflict {
34934            self.write_space();
34935            self.generate_expression(on_conflict)?;
34936        }
34937        for opt in &e.options {
34938            self.write_space();
34939            self.generate_expression(opt)?;
34940        }
34941        Ok(())
34942    }
34943
34944    fn generate_unique_key_property(&mut self, e: &UniqueKeyProperty) -> Result<()> {
34945        // UNIQUE KEY (expressions)
34946        self.write_keyword("UNIQUE KEY");
34947        self.write(" (");
34948        for (i, expr) in e.expressions.iter().enumerate() {
34949            if i > 0 {
34950                self.write(", ");
34951            }
34952            self.generate_expression(expr)?;
34953        }
34954        self.write(")");
34955        Ok(())
34956    }
34957
34958    fn generate_rollup_property(&mut self, e: &RollupProperty) -> Result<()> {
34959        // ROLLUP (r1(col1, col2), r2(col1))
34960        self.write_keyword("ROLLUP");
34961        self.write(" (");
34962        for (i, index) in e.expressions.iter().enumerate() {
34963            if i > 0 {
34964                self.write(", ");
34965            }
34966            self.generate_identifier(&index.name)?;
34967            self.write("(");
34968            for (j, col) in index.expressions.iter().enumerate() {
34969                if j > 0 {
34970                    self.write(", ");
34971                }
34972                self.generate_identifier(col)?;
34973            }
34974            self.write(")");
34975        }
34976        self.write(")");
34977        Ok(())
34978    }
34979
34980    fn generate_unix_to_str(&mut self, e: &UnixToStr) -> Result<()> {
34981        match self.config.dialect {
34982            Some(DialectType::DuckDB) => {
34983                // DuckDB: STRFTIME(TO_TIMESTAMP(value), format)
34984                self.write_keyword("STRFTIME");
34985                self.write("(");
34986                self.write_keyword("TO_TIMESTAMP");
34987                self.write("(");
34988                self.generate_expression(&e.this)?;
34989                self.write("), '");
34990                if let Some(format) = &e.format {
34991                    self.write(format);
34992                }
34993                self.write("')");
34994            }
34995            Some(DialectType::Hive) => {
34996                // Hive: FROM_UNIXTIME(value, format) - elide format when it's the default
34997                self.write_keyword("FROM_UNIXTIME");
34998                self.write("(");
34999                self.generate_expression(&e.this)?;
35000                if let Some(format) = &e.format {
35001                    if format != "yyyy-MM-dd HH:mm:ss" {
35002                        self.write(", '");
35003                        self.write(format);
35004                        self.write("'");
35005                    }
35006                }
35007                self.write(")");
35008            }
35009            Some(DialectType::Presto) | Some(DialectType::Trino) => {
35010                // Presto: DATE_FORMAT(FROM_UNIXTIME(value), format)
35011                self.write_keyword("DATE_FORMAT");
35012                self.write("(");
35013                self.write_keyword("FROM_UNIXTIME");
35014                self.write("(");
35015                self.generate_expression(&e.this)?;
35016                self.write("), '");
35017                if let Some(format) = &e.format {
35018                    self.write(format);
35019                }
35020                self.write("')");
35021            }
35022            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
35023                // Spark: FROM_UNIXTIME(value, format)
35024                self.write_keyword("FROM_UNIXTIME");
35025                self.write("(");
35026                self.generate_expression(&e.this)?;
35027                if let Some(format) = &e.format {
35028                    self.write(", '");
35029                    self.write(format);
35030                    self.write("'");
35031                }
35032                self.write(")");
35033            }
35034            _ => {
35035                // Default: UNIX_TO_STR(this, [format])
35036                self.write_keyword("UNIX_TO_STR");
35037                self.write("(");
35038                self.generate_expression(&e.this)?;
35039                if let Some(format) = &e.format {
35040                    self.write(", '");
35041                    self.write(format);
35042                    self.write("'");
35043                }
35044                self.write(")");
35045            }
35046        }
35047        Ok(())
35048    }
35049
35050    fn generate_unix_to_time(&mut self, e: &UnixToTime) -> Result<()> {
35051        use crate::dialects::DialectType;
35052        let scale = e.scale.unwrap_or(0); // 0 = seconds
35053
35054        match self.config.dialect {
35055            Some(DialectType::Snowflake) => {
35056                // Snowflake: TO_TIMESTAMP(value[, scale]) - skip scale for seconds (0)
35057                self.write_keyword("TO_TIMESTAMP");
35058                self.write("(");
35059                self.generate_expression(&e.this)?;
35060                if let Some(s) = e.scale {
35061                    if s > 0 {
35062                        self.write(", ");
35063                        self.write(&s.to_string());
35064                    }
35065                }
35066                self.write(")");
35067            }
35068            Some(DialectType::BigQuery) => {
35069                // BigQuery: TIMESTAMP_SECONDS(value) / TIMESTAMP_MILLIS(value)
35070                // or TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64)) for other scales
35071                match scale {
35072                    0 => {
35073                        self.write_keyword("TIMESTAMP_SECONDS");
35074                        self.write("(");
35075                        self.generate_expression(&e.this)?;
35076                        self.write(")");
35077                    }
35078                    3 => {
35079                        self.write_keyword("TIMESTAMP_MILLIS");
35080                        self.write("(");
35081                        self.generate_expression(&e.this)?;
35082                        self.write(")");
35083                    }
35084                    6 => {
35085                        self.write_keyword("TIMESTAMP_MICROS");
35086                        self.write("(");
35087                        self.generate_expression(&e.this)?;
35088                        self.write(")");
35089                    }
35090                    _ => {
35091                        // TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64))
35092                        self.write_keyword("TIMESTAMP_SECONDS");
35093                        self.write("(CAST(");
35094                        self.generate_expression(&e.this)?;
35095                        self.write(&format!(" / POWER(10, {}) AS INT64))", scale));
35096                    }
35097                }
35098            }
35099            Some(DialectType::Spark) => {
35100                // Spark: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
35101                // TIMESTAMP_MILLIS(value) for scale=3
35102                // TIMESTAMP_MICROS(value) for scale=6
35103                // TIMESTAMP_SECONDS(value / POWER(10, scale)) for other scales
35104                match scale {
35105                    0 => {
35106                        self.write_keyword("CAST");
35107                        self.write("(");
35108                        self.write_keyword("FROM_UNIXTIME");
35109                        self.write("(");
35110                        self.generate_expression(&e.this)?;
35111                        self.write(") ");
35112                        self.write_keyword("AS TIMESTAMP");
35113                        self.write(")");
35114                    }
35115                    3 => {
35116                        self.write_keyword("TIMESTAMP_MILLIS");
35117                        self.write("(");
35118                        self.generate_expression(&e.this)?;
35119                        self.write(")");
35120                    }
35121                    6 => {
35122                        self.write_keyword("TIMESTAMP_MICROS");
35123                        self.write("(");
35124                        self.generate_expression(&e.this)?;
35125                        self.write(")");
35126                    }
35127                    _ => {
35128                        self.write_keyword("TIMESTAMP_SECONDS");
35129                        self.write("(");
35130                        self.generate_expression(&e.this)?;
35131                        self.write(&format!(" / POWER(10, {}))", scale));
35132                    }
35133                }
35134            }
35135            Some(DialectType::Databricks) => {
35136                // Databricks: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
35137                // TIMESTAMP_MILLIS(value) for scale=3
35138                // TIMESTAMP_MICROS(value) for scale=6
35139                match scale {
35140                    0 => {
35141                        self.write_keyword("CAST");
35142                        self.write("(");
35143                        self.write_keyword("FROM_UNIXTIME");
35144                        self.write("(");
35145                        self.generate_expression(&e.this)?;
35146                        self.write(") ");
35147                        self.write_keyword("AS TIMESTAMP");
35148                        self.write(")");
35149                    }
35150                    3 => {
35151                        self.write_keyword("TIMESTAMP_MILLIS");
35152                        self.write("(");
35153                        self.generate_expression(&e.this)?;
35154                        self.write(")");
35155                    }
35156                    6 => {
35157                        self.write_keyword("TIMESTAMP_MICROS");
35158                        self.write("(");
35159                        self.generate_expression(&e.this)?;
35160                        self.write(")");
35161                    }
35162                    _ => {
35163                        self.write_keyword("TIMESTAMP_SECONDS");
35164                        self.write("(");
35165                        self.generate_expression(&e.this)?;
35166                        self.write(&format!(" / POWER(10, {}))", scale));
35167                    }
35168                }
35169            }
35170            Some(DialectType::Hive) => {
35171                // Hive: FROM_UNIXTIME(value)
35172                if scale == 0 {
35173                    self.write_keyword("FROM_UNIXTIME");
35174                    self.write("(");
35175                    self.generate_expression(&e.this)?;
35176                    self.write(")");
35177                } else {
35178                    self.write_keyword("FROM_UNIXTIME");
35179                    self.write("(");
35180                    self.generate_expression(&e.this)?;
35181                    self.write(&format!(" / POWER(10, {})", scale));
35182                    self.write(")");
35183                }
35184            }
35185            Some(DialectType::Presto) | Some(DialectType::Trino) => {
35186                // Presto: FROM_UNIXTIME(CAST(value AS DOUBLE) / POW(10, scale)) for scale > 0
35187                // FROM_UNIXTIME(value) for scale=0
35188                if scale == 0 {
35189                    self.write_keyword("FROM_UNIXTIME");
35190                    self.write("(");
35191                    self.generate_expression(&e.this)?;
35192                    self.write(")");
35193                } else {
35194                    self.write_keyword("FROM_UNIXTIME");
35195                    self.write("(CAST(");
35196                    self.generate_expression(&e.this)?;
35197                    self.write(&format!(" AS DOUBLE) / POW(10, {}))", scale));
35198                }
35199            }
35200            Some(DialectType::DuckDB) => {
35201                // DuckDB: TO_TIMESTAMP(value) for scale=0
35202                // EPOCH_MS(value) for scale=3
35203                // MAKE_TIMESTAMP(value) for scale=6
35204                match scale {
35205                    0 => {
35206                        self.write_keyword("TO_TIMESTAMP");
35207                        self.write("(");
35208                        self.generate_expression(&e.this)?;
35209                        self.write(")");
35210                    }
35211                    3 => {
35212                        self.write_keyword("EPOCH_MS");
35213                        self.write("(");
35214                        self.generate_expression(&e.this)?;
35215                        self.write(")");
35216                    }
35217                    6 => {
35218                        self.write_keyword("MAKE_TIMESTAMP");
35219                        self.write("(");
35220                        self.generate_expression(&e.this)?;
35221                        self.write(")");
35222                    }
35223                    _ => {
35224                        self.write_keyword("TO_TIMESTAMP");
35225                        self.write("(");
35226                        self.generate_expression(&e.this)?;
35227                        self.write(&format!(" / POWER(10, {}))", scale));
35228                        self.write_keyword(" AT TIME ZONE");
35229                        self.write(" 'UTC'");
35230                    }
35231                }
35232            }
35233            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
35234                // Doris/StarRocks: FROM_UNIXTIME(value)
35235                self.write_keyword("FROM_UNIXTIME");
35236                self.write("(");
35237                self.generate_expression(&e.this)?;
35238                self.write(")");
35239            }
35240            Some(DialectType::Oracle) => {
35241                // Oracle: TO_DATE('1970-01-01', 'YYYY-MM-DD') + (x / 86400)
35242                self.write("TO_DATE('1970-01-01', 'YYYY-MM-DD') + (");
35243                self.generate_expression(&e.this)?;
35244                self.write(" / 86400)");
35245            }
35246            Some(DialectType::Redshift) => {
35247                // Redshift: (TIMESTAMP 'epoch' + value * INTERVAL '1 SECOND') for scale=0
35248                // (TIMESTAMP 'epoch' + (value / POWER(10, scale)) * INTERVAL '1 SECOND') for scale > 0
35249                self.write("(TIMESTAMP 'epoch' + ");
35250                if scale == 0 {
35251                    self.generate_expression(&e.this)?;
35252                } else {
35253                    self.write("(");
35254                    self.generate_expression(&e.this)?;
35255                    self.write(&format!(" / POWER(10, {}))", scale));
35256                }
35257                self.write(" * INTERVAL '1 SECOND')");
35258            }
35259            _ => {
35260                // Default: TO_TIMESTAMP(value[, scale])
35261                self.write_keyword("TO_TIMESTAMP");
35262                self.write("(");
35263                self.generate_expression(&e.this)?;
35264                if let Some(s) = e.scale {
35265                    self.write(", ");
35266                    self.write(&s.to_string());
35267                }
35268                self.write(")");
35269            }
35270        }
35271        Ok(())
35272    }
35273
35274    fn generate_unpivot_columns(&mut self, e: &UnpivotColumns) -> Result<()> {
35275        // NAME col VALUE col1, col2, ...
35276        if !matches!(&*e.this, Expression::Null(_)) {
35277            self.write_keyword("NAME");
35278            self.write_space();
35279            self.generate_expression(&e.this)?;
35280        }
35281        if !e.expressions.is_empty() {
35282            self.write_space();
35283            self.write_keyword("VALUE");
35284            self.write_space();
35285            for (i, expr) in e.expressions.iter().enumerate() {
35286                if i > 0 {
35287                    self.write(", ");
35288                }
35289                self.generate_expression(expr)?;
35290            }
35291        }
35292        Ok(())
35293    }
35294
35295    fn generate_user_defined_function(&mut self, e: &UserDefinedFunction) -> Result<()> {
35296        // this(expressions) or (this)(expressions)
35297        if e.wrapped.is_some() {
35298            self.write("(");
35299        }
35300        self.generate_expression(&e.this)?;
35301        if e.wrapped.is_some() {
35302            self.write(")");
35303        }
35304        self.write("(");
35305        for (i, expr) in e.expressions.iter().enumerate() {
35306            if i > 0 {
35307                self.write(", ");
35308            }
35309            self.generate_expression(expr)?;
35310        }
35311        self.write(")");
35312        Ok(())
35313    }
35314
35315    fn generate_using_template_property(&mut self, e: &UsingTemplateProperty) -> Result<()> {
35316        // USING TEMPLATE this
35317        self.write_keyword("USING TEMPLATE");
35318        self.write_space();
35319        self.generate_expression(&e.this)?;
35320        Ok(())
35321    }
35322
35323    fn generate_utc_time(&mut self, _e: &UtcTime) -> Result<()> {
35324        // UTC_TIME
35325        self.write_keyword("UTC_TIME");
35326        Ok(())
35327    }
35328
35329    fn generate_utc_timestamp(&mut self, _e: &UtcTimestamp) -> Result<()> {
35330        // UTC_TIMESTAMP
35331        self.write_keyword("UTC_TIMESTAMP");
35332        Ok(())
35333    }
35334
35335    fn generate_uuid(&mut self, e: &Uuid) -> Result<()> {
35336        use crate::dialects::DialectType;
35337        // Choose UUID function name based on target dialect
35338        let func_name = match self.config.dialect {
35339            Some(DialectType::Snowflake) => "UUID_STRING",
35340            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
35341            Some(DialectType::BigQuery) => "GENERATE_UUID",
35342            _ => {
35343                if let Some(name) = &e.name {
35344                    name.as_str()
35345                } else {
35346                    "UUID"
35347                }
35348            }
35349        };
35350        self.write_keyword(func_name);
35351        self.write("(");
35352        if let Some(this) = &e.this {
35353            self.generate_expression(this)?;
35354        }
35355        self.write(")");
35356        Ok(())
35357    }
35358
35359    fn generate_var_map(&mut self, e: &VarMap) -> Result<()> {
35360        // MAP(key1, value1, key2, value2, ...)
35361        self.write_keyword("MAP");
35362        self.write("(");
35363        let mut first = true;
35364        for (k, v) in e.keys.iter().zip(e.values.iter()) {
35365            if !first {
35366                self.write(", ");
35367            }
35368            self.generate_expression(k)?;
35369            self.write(", ");
35370            self.generate_expression(v)?;
35371            first = false;
35372        }
35373        self.write(")");
35374        Ok(())
35375    }
35376
35377    fn generate_vector_search(&mut self, e: &VectorSearch) -> Result<()> {
35378        // VECTOR_SEARCH(this, column_to_search, query_table, query_column_to_search, top_k, distance_type, ...)
35379        self.write_keyword("VECTOR_SEARCH");
35380        self.write("(");
35381        self.generate_expression(&e.this)?;
35382        if let Some(col) = &e.column_to_search {
35383            self.write(", ");
35384            self.generate_expression(col)?;
35385        }
35386        if let Some(query_table) = &e.query_table {
35387            self.write(", ");
35388            self.generate_expression(query_table)?;
35389        }
35390        if let Some(query_col) = &e.query_column_to_search {
35391            self.write(", ");
35392            self.generate_expression(query_col)?;
35393        }
35394        if let Some(top_k) = &e.top_k {
35395            self.write(", ");
35396            self.generate_expression(top_k)?;
35397        }
35398        if let Some(dist_type) = &e.distance_type {
35399            self.write(", ");
35400            self.generate_expression(dist_type)?;
35401        }
35402        self.write(")");
35403        Ok(())
35404    }
35405
35406    fn generate_version(&mut self, e: &Version) -> Result<()> {
35407        // Python: f"FOR {expression.name} {kind} {expr}"
35408        // e.this = Identifier("TIMESTAMP" or "VERSION")
35409        // e.kind = "AS OF" (or "BETWEEN", etc.)
35410        // e.expression = the value expression
35411        // Hive does NOT use the FOR prefix for time travel
35412        use crate::dialects::DialectType;
35413        let skip_for = matches!(
35414            self.config.dialect,
35415            Some(DialectType::Hive) | Some(DialectType::Spark)
35416        );
35417        if !skip_for {
35418            self.write_keyword("FOR");
35419            self.write_space();
35420        }
35421        // Extract the name from this (which is an Identifier expression)
35422        match e.this.as_ref() {
35423            Expression::Identifier(ident) => {
35424                self.write_keyword(&ident.name);
35425            }
35426            _ => {
35427                self.generate_expression(&e.this)?;
35428            }
35429        }
35430        self.write_space();
35431        self.write_keyword(&e.kind);
35432        if let Some(expression) = &e.expression {
35433            self.write_space();
35434            self.generate_expression(expression)?;
35435        }
35436        Ok(())
35437    }
35438
35439    fn generate_view_attribute_property(&mut self, e: &ViewAttributeProperty) -> Result<()> {
35440        // Python: return self.sql(expression, "this")
35441        self.generate_expression(&e.this)?;
35442        Ok(())
35443    }
35444
35445    fn generate_volatile_property(&mut self, e: &VolatileProperty) -> Result<()> {
35446        // Python: return "VOLATILE" if expression.args.get("this") is None else "NOT VOLATILE"
35447        if e.this.is_some() {
35448            self.write_keyword("NOT VOLATILE");
35449        } else {
35450            self.write_keyword("VOLATILE");
35451        }
35452        Ok(())
35453    }
35454
35455    fn generate_watermark_column_constraint(
35456        &mut self,
35457        e: &WatermarkColumnConstraint,
35458    ) -> Result<()> {
35459        // Python: f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
35460        self.write_keyword("WATERMARK FOR");
35461        self.write_space();
35462        self.generate_expression(&e.this)?;
35463        self.write_space();
35464        self.write_keyword("AS");
35465        self.write_space();
35466        self.generate_expression(&e.expression)?;
35467        Ok(())
35468    }
35469
35470    fn generate_week(&mut self, e: &Week) -> Result<()> {
35471        // Python: return self.func("WEEK", expression.this, expression.args.get("mode"))
35472        self.write_keyword("WEEK");
35473        self.write("(");
35474        self.generate_expression(&e.this)?;
35475        if let Some(mode) = &e.mode {
35476            self.write(", ");
35477            self.generate_expression(mode)?;
35478        }
35479        self.write(")");
35480        Ok(())
35481    }
35482
35483    fn generate_when(&mut self, e: &When) -> Result<()> {
35484        // Python: WHEN {matched}{source}{condition} THEN {then}
35485        // matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
35486        // source = " BY SOURCE" if MATCHED_BY_SOURCE and expression.args.get("source") else ""
35487        self.write_keyword("WHEN");
35488        self.write_space();
35489
35490        // Check if matched
35491        if let Some(matched) = &e.matched {
35492            // Check the expression - if it's a boolean true, use MATCHED, otherwise NOT MATCHED
35493            match matched.as_ref() {
35494                Expression::Boolean(b) if b.value => {
35495                    self.write_keyword("MATCHED");
35496                }
35497                _ => {
35498                    self.write_keyword("NOT MATCHED");
35499                }
35500            }
35501        } else {
35502            self.write_keyword("NOT MATCHED");
35503        }
35504
35505        // BY SOURCE / BY TARGET
35506        // source = Boolean(true) means BY SOURCE, Boolean(false) means BY TARGET
35507        // BY TARGET is the default and typically omitted in output
35508        // Only emit if the dialect supports BY SOURCE syntax
35509        if self.config.matched_by_source {
35510            if let Some(source) = &e.source {
35511                if let Expression::Boolean(b) = source.as_ref() {
35512                    if b.value {
35513                        // BY SOURCE
35514                        self.write_space();
35515                        self.write_keyword("BY SOURCE");
35516                    }
35517                    // BY TARGET (b.value == false) is omitted as it's the default
35518                } else {
35519                    // For non-boolean source, output as BY SOURCE (legacy behavior)
35520                    self.write_space();
35521                    self.write_keyword("BY SOURCE");
35522                }
35523            }
35524        }
35525
35526        // Condition
35527        if let Some(condition) = &e.condition {
35528            self.write_space();
35529            self.write_keyword("AND");
35530            self.write_space();
35531            self.generate_expression(condition)?;
35532        }
35533
35534        self.write_space();
35535        self.write_keyword("THEN");
35536        self.write_space();
35537
35538        // Generate the then expression (could be INSERT, UPDATE, DELETE)
35539        // MERGE actions are stored as Tuples with the action keyword as first element
35540        self.generate_merge_action(&e.then)?;
35541
35542        Ok(())
35543    }
35544
35545    fn generate_merge_action(&mut self, action: &Expression) -> Result<()> {
35546        match action {
35547            Expression::Tuple(tuple) => {
35548                let elements = &tuple.expressions;
35549                if elements.is_empty() {
35550                    return self.generate_expression(action);
35551                }
35552                // Check if first element is a Var (INSERT, UPDATE, DELETE, etc.)
35553                match &elements[0] {
35554                    Expression::Var(v) if v.this == "INSERT" => {
35555                        self.write_keyword("INSERT");
35556                        // Spark: INSERT * (insert all columns)
35557                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
35558                            self.write(" *");
35559                        } else {
35560                            let mut values_idx = 1;
35561                            // Check if second element is column list (Tuple)
35562                            if elements.len() > 1 {
35563                                if let Expression::Tuple(cols) = &elements[1] {
35564                                    // Could be columns or values - if there's a third element, second is columns
35565                                    if elements.len() > 2 {
35566                                        // Second is columns, third is values
35567                                        self.write(" (");
35568                                        for (i, col) in cols.expressions.iter().enumerate() {
35569                                            if i > 0 {
35570                                                self.write(", ");
35571                                            }
35572                                            // Strip MERGE target qualifiers from INSERT column list
35573                                            if !self.merge_strip_qualifiers.is_empty() {
35574                                                let stripped = self.strip_merge_qualifier(col);
35575                                                self.generate_expression(&stripped)?;
35576                                            } else {
35577                                                self.generate_expression(col)?;
35578                                            }
35579                                        }
35580                                        self.write(")");
35581                                        values_idx = 2;
35582                                    } else {
35583                                        // Only two elements: INSERT + values (no explicit columns)
35584                                        values_idx = 1;
35585                                    }
35586                                }
35587                            }
35588                            // Generate VALUES clause
35589                            if values_idx < elements.len() {
35590                                // Check if it's INSERT ROW (BigQuery) — no VALUES keyword needed
35591                                let is_row = matches!(&elements[values_idx], Expression::Var(v) if v.this == "ROW");
35592                                if !is_row {
35593                                    self.write_space();
35594                                    self.write_keyword("VALUES");
35595                                }
35596                                self.write(" ");
35597                                if let Expression::Tuple(vals) = &elements[values_idx] {
35598                                    self.write("(");
35599                                    for (i, val) in vals.expressions.iter().enumerate() {
35600                                        if i > 0 {
35601                                            self.write(", ");
35602                                        }
35603                                        self.generate_expression(val)?;
35604                                    }
35605                                    self.write(")");
35606                                } else {
35607                                    self.generate_expression(&elements[values_idx])?;
35608                                }
35609                            }
35610                        } // close else for INSERT * check
35611                    }
35612                    Expression::Var(v) if v.this == "UPDATE" => {
35613                        self.write_keyword("UPDATE");
35614                        // Spark: UPDATE * (update all columns)
35615                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
35616                            self.write(" *");
35617                        } else if elements.len() > 1 {
35618                            self.write_space();
35619                            self.write_keyword("SET");
35620                            // In pretty mode, put assignments on next line with extra indent
35621                            if self.config.pretty {
35622                                self.write_newline();
35623                                self.indent_level += 1;
35624                                self.write_indent();
35625                            } else {
35626                                self.write_space();
35627                            }
35628                            if let Expression::Tuple(assignments) = &elements[1] {
35629                                for (i, assignment) in assignments.expressions.iter().enumerate() {
35630                                    if i > 0 {
35631                                        if self.config.pretty {
35632                                            self.write(",");
35633                                            self.write_newline();
35634                                            self.write_indent();
35635                                        } else {
35636                                            self.write(", ");
35637                                        }
35638                                    }
35639                                    // Strip MERGE target qualifiers from left side of UPDATE SET
35640                                    if !self.merge_strip_qualifiers.is_empty() {
35641                                        self.generate_merge_set_assignment(assignment)?;
35642                                    } else {
35643                                        self.generate_expression(assignment)?;
35644                                    }
35645                                }
35646                            } else {
35647                                self.generate_expression(&elements[1])?;
35648                            }
35649                            if self.config.pretty {
35650                                self.indent_level -= 1;
35651                            }
35652                        }
35653                    }
35654                    _ => {
35655                        // Fallback: generic tuple generation
35656                        self.generate_expression(action)?;
35657                    }
35658                }
35659            }
35660            Expression::Var(v)
35661                if v.this == "INSERT"
35662                    || v.this == "UPDATE"
35663                    || v.this == "DELETE"
35664                    || v.this == "DO NOTHING" =>
35665            {
35666                self.write_keyword(&v.this);
35667            }
35668            _ => {
35669                self.generate_expression(action)?;
35670            }
35671        }
35672        Ok(())
35673    }
35674
35675    /// Generate a MERGE UPDATE SET assignment, stripping target table qualifier from left side
35676    fn generate_merge_set_assignment(&mut self, assignment: &Expression) -> Result<()> {
35677        match assignment {
35678            Expression::Eq(eq) => {
35679                // Strip qualifier from the left side if it matches a MERGE target name
35680                let stripped_left = self.strip_merge_qualifier(&eq.left);
35681                self.generate_expression(&stripped_left)?;
35682                self.write(" = ");
35683                self.generate_expression(&eq.right)?;
35684                Ok(())
35685            }
35686            other => self.generate_expression(other),
35687        }
35688    }
35689
35690    /// Strip table qualifier from a column reference if it matches a MERGE target name
35691    fn strip_merge_qualifier(&self, expr: &Expression) -> Expression {
35692        match expr {
35693            Expression::Column(col) => {
35694                if let Some(ref table_ident) = col.table {
35695                    if self
35696                        .merge_strip_qualifiers
35697                        .iter()
35698                        .any(|n| n.eq_ignore_ascii_case(&table_ident.name))
35699                    {
35700                        // Strip the table qualifier
35701                        let mut col = col.clone();
35702                        col.table = None;
35703                        return Expression::Column(col);
35704                    }
35705                }
35706                expr.clone()
35707            }
35708            Expression::Dot(dot) => {
35709                // table.column -> column (strip qualifier)
35710                if let Expression::Identifier(id) = &dot.this {
35711                    if self
35712                        .merge_strip_qualifiers
35713                        .iter()
35714                        .any(|n| n.eq_ignore_ascii_case(&id.name))
35715                    {
35716                        return Expression::Identifier(dot.field.clone());
35717                    }
35718                }
35719                expr.clone()
35720            }
35721            _ => expr.clone(),
35722        }
35723    }
35724
35725    fn generate_whens(&mut self, e: &Whens) -> Result<()> {
35726        // Python: return self.expressions(expression, sep=" ", indent=False)
35727        for (i, expr) in e.expressions.iter().enumerate() {
35728            if i > 0 {
35729                // In pretty mode, each WHEN clause on its own line
35730                if self.config.pretty {
35731                    self.write_newline();
35732                    self.write_indent();
35733                } else {
35734                    self.write_space();
35735                }
35736            }
35737            self.generate_expression(expr)?;
35738        }
35739        Ok(())
35740    }
35741
35742    fn generate_where(&mut self, e: &Where) -> Result<()> {
35743        // Python: return f"{self.seg('WHERE')}{self.sep()}{this}"
35744        self.write_keyword("WHERE");
35745        self.write_space();
35746        self.generate_expression(&e.this)?;
35747        Ok(())
35748    }
35749
35750    fn generate_width_bucket(&mut self, e: &WidthBucket) -> Result<()> {
35751        // Python: return self.func("WIDTH_BUCKET", expression.this, ...)
35752        self.write_keyword("WIDTH_BUCKET");
35753        self.write("(");
35754        self.generate_expression(&e.this)?;
35755        if let Some(min_value) = &e.min_value {
35756            self.write(", ");
35757            self.generate_expression(min_value)?;
35758        }
35759        if let Some(max_value) = &e.max_value {
35760            self.write(", ");
35761            self.generate_expression(max_value)?;
35762        }
35763        if let Some(num_buckets) = &e.num_buckets {
35764            self.write(", ");
35765            self.generate_expression(num_buckets)?;
35766        }
35767        self.write(")");
35768        Ok(())
35769    }
35770
35771    fn generate_window(&mut self, e: &WindowSpec) -> Result<()> {
35772        // Window specification: PARTITION BY ... ORDER BY ... frame
35773        self.generate_window_spec(e)
35774    }
35775
35776    fn generate_window_spec(&mut self, e: &WindowSpec) -> Result<()> {
35777        // Window specification: PARTITION BY ... ORDER BY ... frame
35778        let mut has_content = false;
35779
35780        // PARTITION BY
35781        if !e.partition_by.is_empty() {
35782            self.write_keyword("PARTITION BY");
35783            self.write_space();
35784            for (i, expr) in e.partition_by.iter().enumerate() {
35785                if i > 0 {
35786                    self.write(", ");
35787                }
35788                self.generate_expression(expr)?;
35789            }
35790            has_content = true;
35791        }
35792
35793        // ORDER BY
35794        if !e.order_by.is_empty() {
35795            if has_content {
35796                self.write_space();
35797            }
35798            self.write_keyword("ORDER BY");
35799            self.write_space();
35800            for (i, ordered) in e.order_by.iter().enumerate() {
35801                if i > 0 {
35802                    self.write(", ");
35803                }
35804                self.generate_expression(&ordered.this)?;
35805                if ordered.desc {
35806                    self.write_space();
35807                    self.write_keyword("DESC");
35808                } else if ordered.explicit_asc {
35809                    self.write_space();
35810                    self.write_keyword("ASC");
35811                }
35812                if let Some(nulls_first) = ordered.nulls_first {
35813                    self.write_space();
35814                    self.write_keyword("NULLS");
35815                    self.write_space();
35816                    if nulls_first {
35817                        self.write_keyword("FIRST");
35818                    } else {
35819                        self.write_keyword("LAST");
35820                    }
35821                }
35822            }
35823            has_content = true;
35824        }
35825
35826        // Frame specification
35827        if let Some(frame) = &e.frame {
35828            if has_content {
35829                self.write_space();
35830            }
35831            self.generate_window_frame(frame)?;
35832        }
35833
35834        Ok(())
35835    }
35836
35837    fn generate_with_data_property(&mut self, e: &WithDataProperty) -> Result<()> {
35838        // Python: f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
35839        self.write_keyword("WITH");
35840        self.write_space();
35841        if e.no.is_some() {
35842            self.write_keyword("NO");
35843            self.write_space();
35844        }
35845        self.write_keyword("DATA");
35846
35847        // statistics
35848        if let Some(statistics) = &e.statistics {
35849            self.write_space();
35850            self.write_keyword("AND");
35851            self.write_space();
35852            // Check if statistics is true or false
35853            match statistics.as_ref() {
35854                Expression::Boolean(b) if !b.value => {
35855                    self.write_keyword("NO");
35856                    self.write_space();
35857                }
35858                _ => {}
35859            }
35860            self.write_keyword("STATISTICS");
35861        }
35862        Ok(())
35863    }
35864
35865    fn generate_with_fill(&mut self, e: &WithFill) -> Result<()> {
35866        // Python: f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
35867        self.write_keyword("WITH FILL");
35868
35869        if let Some(from_) = &e.from_ {
35870            self.write_space();
35871            self.write_keyword("FROM");
35872            self.write_space();
35873            self.generate_expression(from_)?;
35874        }
35875
35876        if let Some(to) = &e.to {
35877            self.write_space();
35878            self.write_keyword("TO");
35879            self.write_space();
35880            self.generate_expression(to)?;
35881        }
35882
35883        if let Some(step) = &e.step {
35884            self.write_space();
35885            self.write_keyword("STEP");
35886            self.write_space();
35887            self.generate_expression(step)?;
35888        }
35889
35890        if let Some(staleness) = &e.staleness {
35891            self.write_space();
35892            self.write_keyword("STALENESS");
35893            self.write_space();
35894            self.generate_expression(staleness)?;
35895        }
35896
35897        if let Some(interpolate) = &e.interpolate {
35898            self.write_space();
35899            self.write_keyword("INTERPOLATE");
35900            self.write(" (");
35901            // INTERPOLATE items use reversed alias format: name AS expression
35902            self.generate_interpolate_item(interpolate)?;
35903            self.write(")");
35904        }
35905
35906        Ok(())
35907    }
35908
35909    /// Generate INTERPOLATE items with reversed alias format (name AS expression)
35910    fn generate_interpolate_item(&mut self, expr: &Expression) -> Result<()> {
35911        match expr {
35912            Expression::Alias(alias) => {
35913                // Output as: alias_name AS expression
35914                self.generate_identifier(&alias.alias)?;
35915                self.write_space();
35916                self.write_keyword("AS");
35917                self.write_space();
35918                self.generate_expression(&alias.this)?;
35919            }
35920            Expression::Tuple(tuple) => {
35921                for (i, item) in tuple.expressions.iter().enumerate() {
35922                    if i > 0 {
35923                        self.write(", ");
35924                    }
35925                    self.generate_interpolate_item(item)?;
35926                }
35927            }
35928            other => {
35929                self.generate_expression(other)?;
35930            }
35931        }
35932        Ok(())
35933    }
35934
35935    fn generate_with_journal_table_property(&mut self, e: &WithJournalTableProperty) -> Result<()> {
35936        // Python: return f"WITH JOURNAL TABLE={self.sql(expression, 'this')}"
35937        self.write_keyword("WITH JOURNAL TABLE");
35938        self.write("=");
35939        self.generate_expression(&e.this)?;
35940        Ok(())
35941    }
35942
35943    fn generate_with_operator(&mut self, e: &WithOperator) -> Result<()> {
35944        // Python: return f"{self.sql(expression, 'this')} WITH {self.sql(expression, 'op')}"
35945        self.generate_expression(&e.this)?;
35946        self.write_space();
35947        self.write_keyword("WITH");
35948        self.write_space();
35949        self.write_keyword(&e.op);
35950        Ok(())
35951    }
35952
35953    fn generate_with_procedure_options(&mut self, e: &WithProcedureOptions) -> Result<()> {
35954        // Python: return f"WITH {self.expressions(expression, flat=True)}"
35955        self.write_keyword("WITH");
35956        self.write_space();
35957        for (i, expr) in e.expressions.iter().enumerate() {
35958            if i > 0 {
35959                self.write(", ");
35960            }
35961            self.generate_expression(expr)?;
35962        }
35963        Ok(())
35964    }
35965
35966    fn generate_with_schema_binding_property(
35967        &mut self,
35968        e: &WithSchemaBindingProperty,
35969    ) -> Result<()> {
35970        // Python: return f"WITH {self.sql(expression, 'this')}"
35971        self.write_keyword("WITH");
35972        self.write_space();
35973        self.generate_expression(&e.this)?;
35974        Ok(())
35975    }
35976
35977    fn generate_with_system_versioning_property(
35978        &mut self,
35979        e: &WithSystemVersioningProperty,
35980    ) -> Result<()> {
35981        // Python: complex logic for SYSTEM_VERSIONING with options
35982        // SYSTEM_VERSIONING=ON(HISTORY_TABLE=..., DATA_CONSISTENCY_CHECK=..., HISTORY_RETENTION_PERIOD=...)
35983        // or SYSTEM_VERSIONING=ON/OFF
35984        // with WITH(...) wrapper if with_ is set
35985
35986        let mut parts = Vec::new();
35987
35988        if let Some(this) = &e.this {
35989            // HISTORY_TABLE=...
35990            let mut s = String::from("HISTORY_TABLE=");
35991            let mut gen = Generator::new();
35992            gen.generate_expression(this)?;
35993            s.push_str(&gen.output);
35994            parts.push(s);
35995        }
35996
35997        if let Some(data_consistency) = &e.data_consistency {
35998            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
35999            let mut gen = Generator::new();
36000            gen.generate_expression(data_consistency)?;
36001            s.push_str(&gen.output);
36002            parts.push(s);
36003        }
36004
36005        if let Some(retention_period) = &e.retention_period {
36006            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
36007            let mut gen = Generator::new();
36008            gen.generate_expression(retention_period)?;
36009            s.push_str(&gen.output);
36010            parts.push(s);
36011        }
36012
36013        self.write_keyword("SYSTEM_VERSIONING");
36014        self.write("=");
36015
36016        if !parts.is_empty() {
36017            self.write_keyword("ON");
36018            self.write("(");
36019            self.write(&parts.join(", "));
36020            self.write(")");
36021        } else if e.on.is_some() {
36022            self.write_keyword("ON");
36023        } else {
36024            self.write_keyword("OFF");
36025        }
36026
36027        // Wrap in WITH(...) if with_ is set
36028        if e.with_.is_some() {
36029            let inner = self.output.clone();
36030            self.output.clear();
36031            self.write("WITH(");
36032            self.write(&inner);
36033            self.write(")");
36034        }
36035
36036        Ok(())
36037    }
36038
36039    fn generate_with_table_hint(&mut self, e: &WithTableHint) -> Result<()> {
36040        // Python: f"WITH ({self.expressions(expression, flat=True)})"
36041        self.write_keyword("WITH");
36042        self.write(" (");
36043        for (i, expr) in e.expressions.iter().enumerate() {
36044            if i > 0 {
36045                self.write(", ");
36046            }
36047            self.generate_expression(expr)?;
36048        }
36049        self.write(")");
36050        Ok(())
36051    }
36052
36053    fn generate_xml_element(&mut self, e: &XMLElement) -> Result<()> {
36054        // Python: prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
36055        // return self.func("XMLELEMENT", name, *expression.expressions)
36056        self.write_keyword("XMLELEMENT");
36057        self.write("(");
36058
36059        if e.evalname.is_some() {
36060            self.write_keyword("EVALNAME");
36061        } else {
36062            self.write_keyword("NAME");
36063        }
36064        self.write_space();
36065        self.generate_expression(&e.this)?;
36066
36067        for expr in &e.expressions {
36068            self.write(", ");
36069            self.generate_expression(expr)?;
36070        }
36071        self.write(")");
36072        Ok(())
36073    }
36074
36075    fn generate_xml_get(&mut self, e: &XMLGet) -> Result<()> {
36076        // XMLGET(this, expression [, instance])
36077        self.write_keyword("XMLGET");
36078        self.write("(");
36079        self.generate_expression(&e.this)?;
36080        self.write(", ");
36081        self.generate_expression(&e.expression)?;
36082        if let Some(instance) = &e.instance {
36083            self.write(", ");
36084            self.generate_expression(instance)?;
36085        }
36086        self.write(")");
36087        Ok(())
36088    }
36089
36090    fn generate_xml_key_value_option(&mut self, e: &XMLKeyValueOption) -> Result<()> {
36091        // Python: this + optional (expr)
36092        self.generate_expression(&e.this)?;
36093        if let Some(expression) = &e.expression {
36094            self.write("(");
36095            self.generate_expression(expression)?;
36096            self.write(")");
36097        }
36098        Ok(())
36099    }
36100
36101    fn generate_xml_table(&mut self, e: &XMLTable) -> Result<()> {
36102        // Python: XMLTABLE(namespaces + this + passing + by_ref + columns)
36103        self.write_keyword("XMLTABLE");
36104        self.write("(");
36105
36106        if self.config.pretty {
36107            self.indent_level += 1;
36108            self.write_newline();
36109            self.write_indent();
36110            self.generate_expression(&e.this)?;
36111
36112            if let Some(passing) = &e.passing {
36113                self.write_newline();
36114                self.write_indent();
36115                self.write_keyword("PASSING");
36116                if let Expression::Tuple(tuple) = passing.as_ref() {
36117                    for expr in &tuple.expressions {
36118                        self.write_newline();
36119                        self.indent_level += 1;
36120                        self.write_indent();
36121                        self.generate_expression(expr)?;
36122                        self.indent_level -= 1;
36123                    }
36124                } else {
36125                    self.write_newline();
36126                    self.indent_level += 1;
36127                    self.write_indent();
36128                    self.generate_expression(passing)?;
36129                    self.indent_level -= 1;
36130                }
36131            }
36132
36133            if e.by_ref.is_some() {
36134                self.write_newline();
36135                self.write_indent();
36136                self.write_keyword("RETURNING SEQUENCE BY REF");
36137            }
36138
36139            if !e.columns.is_empty() {
36140                self.write_newline();
36141                self.write_indent();
36142                self.write_keyword("COLUMNS");
36143                for (i, col) in e.columns.iter().enumerate() {
36144                    self.write_newline();
36145                    self.indent_level += 1;
36146                    self.write_indent();
36147                    self.generate_expression(col)?;
36148                    self.indent_level -= 1;
36149                    if i < e.columns.len() - 1 {
36150                        self.write(",");
36151                    }
36152                }
36153            }
36154
36155            self.indent_level -= 1;
36156            self.write_newline();
36157            self.write_indent();
36158            self.write(")");
36159            return Ok(());
36160        }
36161
36162        // Namespaces - unwrap Tuple to generate comma-separated list without parentheses
36163        if let Some(namespaces) = &e.namespaces {
36164            self.write_keyword("XMLNAMESPACES");
36165            self.write("(");
36166            // Unwrap Tuple if present to avoid extra parentheses
36167            if let Expression::Tuple(tuple) = namespaces.as_ref() {
36168                for (i, expr) in tuple.expressions.iter().enumerate() {
36169                    if i > 0 {
36170                        self.write(", ");
36171                    }
36172                    // Python pattern: if it's an Alias, output as-is; otherwise prepend DEFAULT
36173                    // See xmlnamespace_sql in generator.py
36174                    if !matches!(expr, Expression::Alias(_)) {
36175                        self.write_keyword("DEFAULT");
36176                        self.write_space();
36177                    }
36178                    self.generate_expression(expr)?;
36179                }
36180            } else {
36181                // Single namespace - check if DEFAULT
36182                if !matches!(namespaces.as_ref(), Expression::Alias(_)) {
36183                    self.write_keyword("DEFAULT");
36184                    self.write_space();
36185                }
36186                self.generate_expression(namespaces)?;
36187            }
36188            self.write("), ");
36189        }
36190
36191        // XPath expression
36192        self.generate_expression(&e.this)?;
36193
36194        // PASSING clause - unwrap Tuple to generate comma-separated list without parentheses
36195        if let Some(passing) = &e.passing {
36196            self.write_space();
36197            self.write_keyword("PASSING");
36198            self.write_space();
36199            // Unwrap Tuple if present to avoid extra parentheses
36200            if let Expression::Tuple(tuple) = passing.as_ref() {
36201                for (i, expr) in tuple.expressions.iter().enumerate() {
36202                    if i > 0 {
36203                        self.write(", ");
36204                    }
36205                    self.generate_expression(expr)?;
36206                }
36207            } else {
36208                self.generate_expression(passing)?;
36209            }
36210        }
36211
36212        // RETURNING SEQUENCE BY REF
36213        if e.by_ref.is_some() {
36214            self.write_space();
36215            self.write_keyword("RETURNING SEQUENCE BY REF");
36216        }
36217
36218        // COLUMNS clause
36219        if !e.columns.is_empty() {
36220            self.write_space();
36221            self.write_keyword("COLUMNS");
36222            self.write_space();
36223            for (i, col) in e.columns.iter().enumerate() {
36224                if i > 0 {
36225                    self.write(", ");
36226                }
36227                self.generate_expression(col)?;
36228            }
36229        }
36230
36231        self.write(")");
36232        Ok(())
36233    }
36234
36235    fn generate_xor(&mut self, e: &Xor) -> Result<()> {
36236        // Python: return self.connector_sql(expression, "XOR", stack)
36237        // Handles: this XOR expression or expressions joined by XOR
36238        if let Some(this) = &e.this {
36239            self.generate_expression(this)?;
36240            if let Some(expression) = &e.expression {
36241                self.write_space();
36242                self.write_keyword("XOR");
36243                self.write_space();
36244                self.generate_expression(expression)?;
36245            }
36246        }
36247
36248        // Handle multiple expressions
36249        for (i, expr) in e.expressions.iter().enumerate() {
36250            if i > 0 || e.this.is_some() {
36251                self.write_space();
36252                self.write_keyword("XOR");
36253                self.write_space();
36254            }
36255            self.generate_expression(expr)?;
36256        }
36257        Ok(())
36258    }
36259
36260    fn generate_zipf(&mut self, e: &Zipf) -> Result<()> {
36261        // ZIPF(this, elementcount [, gen])
36262        self.write_keyword("ZIPF");
36263        self.write("(");
36264        self.generate_expression(&e.this)?;
36265        if let Some(elementcount) = &e.elementcount {
36266            self.write(", ");
36267            self.generate_expression(elementcount)?;
36268        }
36269        if let Some(gen) = &e.gen {
36270            self.write(", ");
36271            self.generate_expression(gen)?;
36272        }
36273        self.write(")");
36274        Ok(())
36275    }
36276}
36277
36278impl Default for Generator {
36279    fn default() -> Self {
36280        Self::new()
36281    }
36282}
36283
36284#[cfg(test)]
36285mod tests {
36286    use super::*;
36287    use crate::parser::Parser;
36288
36289    fn roundtrip(sql: &str) -> String {
36290        let ast = Parser::parse_sql(sql).unwrap();
36291        Generator::sql(&ast[0]).unwrap()
36292    }
36293
36294    #[test]
36295    fn test_simple_select() {
36296        let result = roundtrip("SELECT 1");
36297        assert_eq!(result, "SELECT 1");
36298    }
36299
36300    #[test]
36301    fn test_select_from() {
36302        let result = roundtrip("SELECT a, b FROM t");
36303        assert_eq!(result, "SELECT a, b FROM t");
36304    }
36305
36306    #[test]
36307    fn test_select_where() {
36308        let result = roundtrip("SELECT * FROM t WHERE x = 1");
36309        assert_eq!(result, "SELECT * FROM t WHERE x = 1");
36310    }
36311
36312    #[test]
36313    fn test_select_join() {
36314        let result = roundtrip("SELECT * FROM a JOIN b ON a.id = b.id");
36315        assert_eq!(result, "SELECT * FROM a JOIN b ON a.id = b.id");
36316    }
36317
36318    #[test]
36319    fn test_insert() {
36320        let result = roundtrip("INSERT INTO t (a, b) VALUES (1, 2)");
36321        assert_eq!(result, "INSERT INTO t (a, b) VALUES (1, 2)");
36322    }
36323
36324    #[test]
36325    fn test_pretty_print() {
36326        let ast = Parser::parse_sql("SELECT a, b FROM t WHERE x = 1").unwrap();
36327        let result = Generator::pretty_sql(&ast[0]).unwrap();
36328        assert!(result.contains('\n'));
36329    }
36330
36331    #[test]
36332    fn test_window_function() {
36333        let result = roundtrip("SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)");
36334        assert_eq!(
36335            result,
36336            "SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)"
36337        );
36338    }
36339
36340    #[test]
36341    fn test_window_function_with_frame() {
36342        let result = roundtrip("SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
36343        assert_eq!(result, "SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
36344    }
36345
36346    #[test]
36347    fn test_aggregate_with_filter() {
36348        let result = roundtrip("SELECT COUNT(*) FILTER (WHERE status = 1) FROM orders");
36349        assert_eq!(
36350            result,
36351            "SELECT COUNT(*) FILTER(WHERE status = 1) FROM orders"
36352        );
36353    }
36354
36355    #[test]
36356    fn test_subscript() {
36357        let result = roundtrip("SELECT arr[0]");
36358        assert_eq!(result, "SELECT arr[0]");
36359    }
36360
36361    // DDL tests
36362    #[test]
36363    fn test_create_table() {
36364        let result = roundtrip("CREATE TABLE users (id INT, name VARCHAR(100))");
36365        assert_eq!(result, "CREATE TABLE users (id INT, name VARCHAR(100))");
36366    }
36367
36368    #[test]
36369    fn test_create_table_with_constraints() {
36370        let result = roundtrip(
36371            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)",
36372        );
36373        assert_eq!(
36374            result,
36375            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)"
36376        );
36377    }
36378
36379    #[test]
36380    fn test_create_table_if_not_exists() {
36381        let result = roundtrip("CREATE TABLE IF NOT EXISTS t (id INT)");
36382        assert_eq!(result, "CREATE TABLE IF NOT EXISTS t (id INT)");
36383    }
36384
36385    #[test]
36386    fn test_drop_table() {
36387        let result = roundtrip("DROP TABLE users");
36388        assert_eq!(result, "DROP TABLE users");
36389    }
36390
36391    #[test]
36392    fn test_drop_table_if_exists_cascade() {
36393        let result = roundtrip("DROP TABLE IF EXISTS users CASCADE");
36394        assert_eq!(result, "DROP TABLE IF EXISTS users CASCADE");
36395    }
36396
36397    #[test]
36398    fn test_alter_table_add_column() {
36399        let result = roundtrip("ALTER TABLE users ADD COLUMN email VARCHAR(255)");
36400        assert_eq!(result, "ALTER TABLE users ADD COLUMN email VARCHAR(255)");
36401    }
36402
36403    #[test]
36404    fn test_alter_table_drop_column() {
36405        let result = roundtrip("ALTER TABLE users DROP COLUMN email");
36406        assert_eq!(result, "ALTER TABLE users DROP COLUMN email");
36407    }
36408
36409    #[test]
36410    fn test_create_index() {
36411        let result = roundtrip("CREATE INDEX idx_name ON users(name)");
36412        assert_eq!(result, "CREATE INDEX idx_name ON users(name)");
36413    }
36414
36415    #[test]
36416    fn test_create_unique_index() {
36417        let result = roundtrip("CREATE UNIQUE INDEX idx_email ON users(email)");
36418        assert_eq!(result, "CREATE UNIQUE INDEX idx_email ON users(email)");
36419    }
36420
36421    #[test]
36422    fn test_drop_index() {
36423        let result = roundtrip("DROP INDEX idx_name");
36424        assert_eq!(result, "DROP INDEX idx_name");
36425    }
36426
36427    #[test]
36428    fn test_create_view() {
36429        let result = roundtrip("CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1");
36430        assert_eq!(
36431            result,
36432            "CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1"
36433        );
36434    }
36435
36436    #[test]
36437    fn test_drop_view() {
36438        let result = roundtrip("DROP VIEW active_users");
36439        assert_eq!(result, "DROP VIEW active_users");
36440    }
36441
36442    #[test]
36443    fn test_truncate() {
36444        let result = roundtrip("TRUNCATE TABLE users");
36445        assert_eq!(result, "TRUNCATE TABLE users");
36446    }
36447
36448    #[test]
36449    fn test_string_literal_escaping_default() {
36450        // Default: double single quotes
36451        let result = roundtrip("SELECT 'hello'");
36452        assert_eq!(result, "SELECT 'hello'");
36453
36454        // Single quotes are doubled
36455        let result = roundtrip("SELECT 'it''s a test'");
36456        assert_eq!(result, "SELECT 'it''s a test'");
36457    }
36458
36459    #[test]
36460    fn test_not_in_style_prefix_default_generic() {
36461        let result = roundtrip("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')");
36462        assert_eq!(
36463            result,
36464            "SELECT id FROM users WHERE NOT status IN ('deleted', 'banned')"
36465        );
36466    }
36467
36468    #[test]
36469    fn test_not_in_style_infix_generic_override() {
36470        let ast =
36471            Parser::parse_sql("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')")
36472                .unwrap();
36473        let config = GeneratorConfig {
36474            not_in_style: NotInStyle::Infix,
36475            ..Default::default()
36476        };
36477        let mut gen = Generator::with_config(config);
36478        let result = gen.generate(&ast[0]).unwrap();
36479        assert_eq!(
36480            result,
36481            "SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')"
36482        );
36483    }
36484
36485    #[test]
36486    fn test_string_literal_escaping_mysql() {
36487        use crate::dialects::DialectType;
36488
36489        let config = GeneratorConfig {
36490            dialect: Some(DialectType::MySQL),
36491            ..Default::default()
36492        };
36493
36494        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
36495        let mut gen = Generator::with_config(config.clone());
36496        let result = gen.generate(&ast[0]).unwrap();
36497        assert_eq!(result, "SELECT 'hello'");
36498
36499        // MySQL uses SQL standard quote doubling for escaping (matches Python sqlglot)
36500        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
36501        let mut gen = Generator::with_config(config.clone());
36502        let result = gen.generate(&ast[0]).unwrap();
36503        assert_eq!(result, "SELECT 'it''s'");
36504    }
36505
36506    #[test]
36507    fn test_string_literal_escaping_postgres() {
36508        use crate::dialects::DialectType;
36509
36510        let config = GeneratorConfig {
36511            dialect: Some(DialectType::PostgreSQL),
36512            ..Default::default()
36513        };
36514
36515        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
36516        let mut gen = Generator::with_config(config.clone());
36517        let result = gen.generate(&ast[0]).unwrap();
36518        assert_eq!(result, "SELECT 'hello'");
36519
36520        // PostgreSQL uses doubled quotes for regular strings
36521        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
36522        let mut gen = Generator::with_config(config.clone());
36523        let result = gen.generate(&ast[0]).unwrap();
36524        assert_eq!(result, "SELECT 'it''s'");
36525    }
36526
36527    #[test]
36528    fn test_string_literal_escaping_bigquery() {
36529        use crate::dialects::DialectType;
36530
36531        let config = GeneratorConfig {
36532            dialect: Some(DialectType::BigQuery),
36533            ..Default::default()
36534        };
36535
36536        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
36537        let mut gen = Generator::with_config(config.clone());
36538        let result = gen.generate(&ast[0]).unwrap();
36539        assert_eq!(result, "SELECT 'hello'");
36540
36541        // BigQuery escapes single quotes with backslash
36542        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
36543        let mut gen = Generator::with_config(config.clone());
36544        let result = gen.generate(&ast[0]).unwrap();
36545        assert_eq!(result, "SELECT 'it\\'s'");
36546    }
36547}