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        let keep_top_verbatim = !is_top_dialect
3899            && select.limit.is_none()
3900            && select
3901                .top
3902                .as_ref()
3903                .map_or(false, |top| top.percent || top.with_ties);
3904
3905        if select.distinct && (is_top_dialect || select.top.is_some()) {
3906            self.write_space();
3907            self.write_keyword("DISTINCT");
3908        }
3909
3910        if is_top_dialect || keep_top_verbatim {
3911            if let Some(top) = &select.top {
3912                self.write_space();
3913                self.write_keyword("TOP");
3914                if top.parenthesized {
3915                    self.write(" (");
3916                    self.generate_expression(&top.this)?;
3917                    self.write(")");
3918                } else {
3919                    self.write_space();
3920                    self.generate_expression(&top.this)?;
3921                }
3922                if top.percent {
3923                    self.write_space();
3924                    self.write_keyword("PERCENT");
3925                }
3926                if top.with_ties {
3927                    self.write_space();
3928                    self.write_keyword("WITH TIES");
3929                }
3930            } else if use_top_from_limit {
3931                // Convert LIMIT to TOP for SQL Server (only when no OFFSET)
3932                if let Some(limit) = &select.limit {
3933                    self.write_space();
3934                    self.write_keyword("TOP");
3935                    // Use parentheses for complex expressions, but not for simple literals
3936                    let is_simple_literal =
3937                        matches!(&limit.this, Expression::Literal(Literal::Number(_)));
3938                    if is_simple_literal {
3939                        self.write_space();
3940                        self.generate_expression(&limit.this)?;
3941                    } else {
3942                        self.write(" (");
3943                        self.generate_expression(&limit.this)?;
3944                        self.write(")");
3945                    }
3946                }
3947            }
3948        }
3949
3950        if select.distinct && !is_top_dialect && select.top.is_none() {
3951            self.write_space();
3952            self.write_keyword("DISTINCT");
3953        }
3954
3955        // DISTINCT ON clause (PostgreSQL)
3956        if let Some(distinct_on) = &select.distinct_on {
3957            self.write_space();
3958            self.write_keyword("ON");
3959            self.write(" (");
3960            for (i, expr) in distinct_on.iter().enumerate() {
3961                if i > 0 {
3962                    self.write(", ");
3963                }
3964                self.generate_expression(expr)?;
3965            }
3966            self.write(")");
3967        }
3968
3969        // MySQL operation modifiers (HIGH_PRIORITY, STRAIGHT_JOIN, SQL_CALC_FOUND_ROWS, etc.)
3970        for modifier in &select.operation_modifiers {
3971            self.write_space();
3972            self.write_keyword(modifier);
3973        }
3974
3975        // BigQuery SELECT AS STRUCT / SELECT AS VALUE
3976        if let Some(kind) = &select.kind {
3977            self.write_space();
3978            self.write_keyword("AS");
3979            self.write_space();
3980            self.write_keyword(kind);
3981        }
3982
3983        // Expressions (only if there are any)
3984        if !select.expressions.is_empty() {
3985            if self.config.pretty {
3986                self.write_newline();
3987                self.indent_level += 1;
3988            } else {
3989                self.write_space();
3990            }
3991        }
3992
3993        for (i, expr) in select.expressions.iter().enumerate() {
3994            if i > 0 {
3995                self.write(",");
3996                if self.config.pretty {
3997                    self.write_newline();
3998                } else {
3999                    self.write_space();
4000                }
4001            }
4002            if self.config.pretty {
4003                self.write_indent();
4004            }
4005            self.generate_expression(expr)?;
4006        }
4007
4008        if self.config.pretty && !select.expressions.is_empty() {
4009            self.indent_level -= 1;
4010        }
4011
4012        // INTO clause (SELECT ... INTO table_name)
4013        // Also handles Oracle PL/SQL: BULK COLLECT INTO v1, v2, ...
4014        if let Some(into) = &select.into {
4015            if self.config.pretty {
4016                self.write_newline();
4017                self.write_indent();
4018            } else {
4019                self.write_space();
4020            }
4021            if into.bulk_collect {
4022                self.write_keyword("BULK COLLECT INTO");
4023            } else {
4024                self.write_keyword("INTO");
4025            }
4026            if into.temporary {
4027                self.write_space();
4028                self.write_keyword("TEMPORARY");
4029            }
4030            if into.unlogged {
4031                self.write_space();
4032                self.write_keyword("UNLOGGED");
4033            }
4034            self.write_space();
4035            // If we have multiple expressions, output them comma-separated
4036            if !into.expressions.is_empty() {
4037                for (i, expr) in into.expressions.iter().enumerate() {
4038                    if i > 0 {
4039                        self.write(", ");
4040                    }
4041                    self.generate_expression(expr)?;
4042                }
4043            } else {
4044                self.generate_expression(&into.this)?;
4045            }
4046        }
4047
4048        // FROM clause
4049        if let Some(from) = &select.from {
4050            if self.config.pretty {
4051                self.write_newline();
4052                self.write_indent();
4053            } else {
4054                self.write_space();
4055            }
4056            self.write_keyword("FROM");
4057            self.write_space();
4058
4059            // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax for multiple tables
4060            // But keep commas when TABLESAMPLE is present (Spark/Hive handle TABLESAMPLE differently with commas)
4061            // Also keep commas when the source dialect is Generic/None and target is one of these dialects
4062            // (Python sqlglot: the Hive/Spark parser marks comma joins as CROSS, but Generic parser keeps them implicit)
4063            let has_tablesample = from
4064                .expressions
4065                .iter()
4066                .any(|e| matches!(e, Expression::TableSample(_)));
4067            let is_cross_join_dialect = matches!(
4068                self.config.dialect,
4069                Some(DialectType::BigQuery)
4070                    | Some(DialectType::Hive)
4071                    | Some(DialectType::Spark)
4072                    | Some(DialectType::Databricks)
4073                    | Some(DialectType::SQLite)
4074                    | Some(DialectType::ClickHouse)
4075            );
4076            // Skip CROSS JOIN conversion when source is Generic/None and target is a CROSS JOIN dialect
4077            // This matches Python sqlglot where comma-to-CROSS-JOIN is done in the dialect's parser, not generator
4078            let source_is_same_as_target = self.config.source_dialect.is_some()
4079                && self.config.source_dialect == self.config.dialect;
4080            let source_is_cross_join_dialect = matches!(
4081                self.config.source_dialect,
4082                Some(DialectType::BigQuery)
4083                    | Some(DialectType::Hive)
4084                    | Some(DialectType::Spark)
4085                    | Some(DialectType::Databricks)
4086                    | Some(DialectType::SQLite)
4087                    | Some(DialectType::ClickHouse)
4088            );
4089            let use_cross_join = !has_tablesample
4090                && is_cross_join_dialect
4091                && (source_is_same_as_target
4092                    || source_is_cross_join_dialect
4093                    || self.config.source_dialect.is_none());
4094
4095            // Snowflake wraps standalone VALUES in FROM clause with parentheses
4096            let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
4097
4098            for (i, expr) in from.expressions.iter().enumerate() {
4099                if i > 0 {
4100                    if use_cross_join {
4101                        self.write(" CROSS JOIN ");
4102                    } else {
4103                        self.write(", ");
4104                    }
4105                }
4106                if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
4107                    self.write("(");
4108                    self.generate_expression(expr)?;
4109                    self.write(")");
4110                } else {
4111                    self.generate_expression(expr)?;
4112                }
4113            }
4114        }
4115
4116        // JOINs - handle nested join structure for pretty printing
4117        // Deferred-condition joins "own" the non-deferred joins that follow them
4118        // until the next deferred join or end of list
4119        if self.config.pretty {
4120            self.generate_joins_with_nesting(&select.joins)?;
4121        } else {
4122            for join in &select.joins {
4123                self.generate_join(join)?;
4124            }
4125            // Output deferred ON/USING conditions (right-to-left, which is reverse order)
4126            for join in select.joins.iter().rev() {
4127                if join.deferred_condition {
4128                    self.generate_join_condition(join)?;
4129                }
4130            }
4131        }
4132
4133        // LATERAL VIEW clauses (Hive/Spark)
4134        for lateral_view in &select.lateral_views {
4135            self.generate_lateral_view(lateral_view)?;
4136        }
4137
4138        // PREWHERE (ClickHouse)
4139        if let Some(prewhere) = &select.prewhere {
4140            self.write_clause_condition("PREWHERE", prewhere)?;
4141        }
4142
4143        // WHERE
4144        if let Some(where_clause) = &select.where_clause {
4145            self.write_clause_condition("WHERE", &where_clause.this)?;
4146        }
4147
4148        // CONNECT BY (Oracle hierarchical queries)
4149        if let Some(connect) = &select.connect {
4150            self.generate_connect(connect)?;
4151        }
4152
4153        // GROUP BY
4154        if let Some(group_by) = &select.group_by {
4155            if self.config.pretty {
4156                // Output leading comments on their own lines before GROUP BY
4157                for comment in &group_by.comments {
4158                    self.write_newline();
4159                    self.write_indent();
4160                    self.write_formatted_comment(comment);
4161                }
4162                self.write_newline();
4163                self.write_indent();
4164            } else {
4165                self.write_space();
4166                // In non-pretty mode, output comments inline
4167                for comment in &group_by.comments {
4168                    self.write_formatted_comment(comment);
4169                    self.write_space();
4170                }
4171            }
4172            self.write_keyword("GROUP BY");
4173            // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
4174            match group_by.all {
4175                Some(true) => {
4176                    self.write_space();
4177                    self.write_keyword("ALL");
4178                }
4179                Some(false) => {
4180                    self.write_space();
4181                    self.write_keyword("DISTINCT");
4182                }
4183                None => {}
4184            }
4185            if !group_by.expressions.is_empty() {
4186                // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
4187                // These are represented as Cube/Rollup expressions with empty expressions at the end
4188                let mut trailing_cube = false;
4189                let mut trailing_rollup = false;
4190                let mut plain_expressions: Vec<&Expression> = Vec::new();
4191                let mut grouping_sets_expressions: Vec<&Expression> = Vec::new();
4192                let mut cube_expressions: Vec<&Expression> = Vec::new();
4193                let mut rollup_expressions: Vec<&Expression> = Vec::new();
4194
4195                for expr in &group_by.expressions {
4196                    match expr {
4197                        Expression::Cube(c) if c.expressions.is_empty() => {
4198                            trailing_cube = true;
4199                        }
4200                        Expression::Rollup(r) if r.expressions.is_empty() => {
4201                            trailing_rollup = true;
4202                        }
4203                        Expression::Function(f) if f.name == "CUBE" => {
4204                            cube_expressions.push(expr);
4205                        }
4206                        Expression::Function(f) if f.name == "ROLLUP" => {
4207                            rollup_expressions.push(expr);
4208                        }
4209                        Expression::Function(f) if f.name == "GROUPING SETS" => {
4210                            grouping_sets_expressions.push(expr);
4211                        }
4212                        _ => {
4213                            plain_expressions.push(expr);
4214                        }
4215                    }
4216                }
4217
4218                // Reorder: plain expressions first, then GROUPING SETS, CUBE, ROLLUP
4219                let mut regular_expressions: Vec<&Expression> = Vec::new();
4220                regular_expressions.extend(plain_expressions);
4221                regular_expressions.extend(grouping_sets_expressions);
4222                regular_expressions.extend(cube_expressions);
4223                regular_expressions.extend(rollup_expressions);
4224
4225                if self.config.pretty {
4226                    self.write_newline();
4227                    self.indent_level += 1;
4228                    self.write_indent();
4229                } else {
4230                    self.write_space();
4231                }
4232
4233                for (i, expr) in regular_expressions.iter().enumerate() {
4234                    if i > 0 {
4235                        if self.config.pretty {
4236                            self.write(",");
4237                            self.write_newline();
4238                            self.write_indent();
4239                        } else {
4240                            self.write(", ");
4241                        }
4242                    }
4243                    self.generate_expression(expr)?;
4244                }
4245
4246                if self.config.pretty {
4247                    self.indent_level -= 1;
4248                }
4249
4250                // Output trailing WITH CUBE or WITH ROLLUP
4251                if trailing_cube {
4252                    self.write_space();
4253                    self.write_keyword("WITH CUBE");
4254                } else if trailing_rollup {
4255                    self.write_space();
4256                    self.write_keyword("WITH ROLLUP");
4257                }
4258            }
4259
4260            // ClickHouse: WITH TOTALS
4261            if group_by.totals {
4262                self.write_space();
4263                self.write_keyword("WITH TOTALS");
4264            }
4265        }
4266
4267        // HAVING
4268        if let Some(having) = &select.having {
4269            if self.config.pretty {
4270                // Output leading comments on their own lines before HAVING
4271                for comment in &having.comments {
4272                    self.write_newline();
4273                    self.write_indent();
4274                    self.write_formatted_comment(comment);
4275                }
4276            } else {
4277                for comment in &having.comments {
4278                    self.write_space();
4279                    self.write_formatted_comment(comment);
4280                }
4281            }
4282            self.write_clause_condition("HAVING", &having.this)?;
4283        }
4284
4285        // QUALIFY and WINDOW clause ordering depends on input SQL
4286        if select.qualify_after_window {
4287            // WINDOW before QUALIFY (DuckDB style)
4288            if let Some(windows) = &select.windows {
4289                self.write_window_clause(windows)?;
4290            }
4291            if let Some(qualify) = &select.qualify {
4292                self.write_clause_condition("QUALIFY", &qualify.this)?;
4293            }
4294        } else {
4295            // QUALIFY before WINDOW (Snowflake/BigQuery default)
4296            if let Some(qualify) = &select.qualify {
4297                self.write_clause_condition("QUALIFY", &qualify.this)?;
4298            }
4299            if let Some(windows) = &select.windows {
4300                self.write_window_clause(windows)?;
4301            }
4302        }
4303
4304        // DISTRIBUTE BY (Hive/Spark)
4305        if let Some(distribute_by) = &select.distribute_by {
4306            self.write_clause_expressions("DISTRIBUTE BY", &distribute_by.expressions)?;
4307        }
4308
4309        // CLUSTER BY (Hive/Spark)
4310        if let Some(cluster_by) = &select.cluster_by {
4311            self.write_order_clause("CLUSTER BY", &cluster_by.expressions)?;
4312        }
4313
4314        // SORT BY (Hive/Spark - comes before ORDER BY)
4315        if let Some(sort_by) = &select.sort_by {
4316            self.write_order_clause("SORT BY", &sort_by.expressions)?;
4317        }
4318
4319        // ORDER BY (or ORDER SIBLINGS BY for Oracle hierarchical queries)
4320        if let Some(order_by) = &select.order_by {
4321            if self.config.pretty {
4322                // Output leading comments on their own lines before ORDER BY
4323                for comment in &order_by.comments {
4324                    self.write_newline();
4325                    self.write_indent();
4326                    self.write_formatted_comment(comment);
4327                }
4328            } else {
4329                for comment in &order_by.comments {
4330                    self.write_space();
4331                    self.write_formatted_comment(comment);
4332                }
4333            }
4334            let keyword = if order_by.siblings {
4335                "ORDER SIBLINGS BY"
4336            } else {
4337                "ORDER BY"
4338            };
4339            self.write_order_clause(keyword, &order_by.expressions)?;
4340        }
4341
4342        // TSQL: FETCH requires ORDER BY. If there's a FETCH but no ORDER BY, add ORDER BY (SELECT NULL) OFFSET 0 ROWS
4343        if select.order_by.is_none()
4344            && select.fetch.is_some()
4345            && matches!(
4346                self.config.dialect,
4347                Some(DialectType::TSQL) | Some(DialectType::Fabric)
4348            )
4349        {
4350            if self.config.pretty {
4351                self.write_newline();
4352                self.write_indent();
4353            } else {
4354                self.write_space();
4355            }
4356            self.write_keyword("ORDER BY (SELECT NULL) OFFSET 0 ROWS");
4357        }
4358
4359        // LIMIT and OFFSET
4360        // PostgreSQL and others use: LIMIT count OFFSET offset
4361        // SQL Server uses: OFFSET ... FETCH (no LIMIT)
4362        // Presto/Trino uses: OFFSET n LIMIT m (offset before limit)
4363        let is_presto_like = matches!(
4364            self.config.dialect,
4365            Some(DialectType::Presto) | Some(DialectType::Trino)
4366        );
4367
4368        if is_presto_like && select.offset.is_some() {
4369            // Presto/Trino syntax: OFFSET n LIMIT m (offset comes first)
4370            if let Some(offset) = &select.offset {
4371                if self.config.pretty {
4372                    self.write_newline();
4373                    self.write_indent();
4374                } else {
4375                    self.write_space();
4376                }
4377                self.write_keyword("OFFSET");
4378                self.write_space();
4379                self.write_limit_expr(&offset.this)?;
4380                if offset.rows == Some(true) {
4381                    self.write_space();
4382                    self.write_keyword("ROWS");
4383                }
4384            }
4385            if let Some(limit) = &select.limit {
4386                if self.config.pretty {
4387                    self.write_newline();
4388                    self.write_indent();
4389                } else {
4390                    self.write_space();
4391                }
4392                self.write_keyword("LIMIT");
4393                self.write_space();
4394                self.write_limit_expr(&limit.this)?;
4395                if limit.percent {
4396                    self.write_space();
4397                    self.write_keyword("PERCENT");
4398                }
4399                // Emit any comments that were captured from before the LIMIT keyword
4400                for comment in &limit.comments {
4401                    self.write(" ");
4402                    self.write_formatted_comment(comment);
4403                }
4404            }
4405        } else {
4406            // Check if FETCH will be converted to LIMIT (used for ordering)
4407            let fetch_as_limit = select.fetch.as_ref().map_or(false, |fetch| {
4408                !fetch.percent
4409                    && !fetch.with_ties
4410                    && fetch.count.is_some()
4411                    && matches!(
4412                        self.config.dialect,
4413                        Some(DialectType::Spark)
4414                            | Some(DialectType::Hive)
4415                            | Some(DialectType::DuckDB)
4416                            | Some(DialectType::SQLite)
4417                            | Some(DialectType::MySQL)
4418                            | Some(DialectType::BigQuery)
4419                            | Some(DialectType::Databricks)
4420                            | Some(DialectType::StarRocks)
4421                            | Some(DialectType::Doris)
4422                            | Some(DialectType::Athena)
4423                            | Some(DialectType::ClickHouse)
4424                            | Some(DialectType::Redshift)
4425                    )
4426            });
4427
4428            // Standard LIMIT clause (skip for SQL Server - we use TOP or OFFSET/FETCH instead)
4429            if let Some(limit) = &select.limit {
4430                // SQL Server uses TOP (no OFFSET) or OFFSET/FETCH (with OFFSET) instead of LIMIT
4431                if !matches!(self.config.dialect, Some(DialectType::TSQL)) {
4432                    if self.config.pretty {
4433                        self.write_newline();
4434                        self.write_indent();
4435                    } else {
4436                        self.write_space();
4437                    }
4438                    self.write_keyword("LIMIT");
4439                    self.write_space();
4440                    self.write_limit_expr(&limit.this)?;
4441                    if limit.percent {
4442                        self.write_space();
4443                        self.write_keyword("PERCENT");
4444                    }
4445                    // Emit any comments that were captured from before the LIMIT keyword
4446                    for comment in &limit.comments {
4447                        self.write(" ");
4448                        self.write_formatted_comment(comment);
4449                    }
4450                }
4451            }
4452
4453            // Convert TOP to LIMIT for non-TOP dialects
4454            if select.top.is_some() && !is_top_dialect && select.limit.is_none() {
4455                if let Some(top) = &select.top {
4456                    if !top.percent && !top.with_ties {
4457                        if self.config.pretty {
4458                            self.write_newline();
4459                            self.write_indent();
4460                        } else {
4461                            self.write_space();
4462                        }
4463                        self.write_keyword("LIMIT");
4464                        self.write_space();
4465                        self.generate_expression(&top.this)?;
4466                    }
4467                }
4468            }
4469
4470            // If FETCH will be converted to LIMIT and there's also OFFSET,
4471            // emit LIMIT from FETCH BEFORE the OFFSET
4472            if fetch_as_limit && select.offset.is_some() {
4473                if let Some(fetch) = &select.fetch {
4474                    if self.config.pretty {
4475                        self.write_newline();
4476                        self.write_indent();
4477                    } else {
4478                        self.write_space();
4479                    }
4480                    self.write_keyword("LIMIT");
4481                    self.write_space();
4482                    self.generate_expression(fetch.count.as_ref().unwrap())?;
4483                }
4484            }
4485
4486            // OFFSET
4487            // In SQL Server, OFFSET requires ORDER BY and uses different syntax
4488            // OFFSET x ROWS FETCH NEXT y ROWS ONLY
4489            if let Some(offset) = &select.offset {
4490                if self.config.pretty {
4491                    self.write_newline();
4492                    self.write_indent();
4493                } else {
4494                    self.write_space();
4495                }
4496                if matches!(self.config.dialect, Some(DialectType::TSQL)) {
4497                    // SQL Server 2012+ OFFSET ... FETCH syntax
4498                    self.write_keyword("OFFSET");
4499                    self.write_space();
4500                    self.write_limit_expr(&offset.this)?;
4501                    self.write_space();
4502                    self.write_keyword("ROWS");
4503                    // If there was a LIMIT, use FETCH NEXT ... ROWS ONLY
4504                    if let Some(limit) = &select.limit {
4505                        self.write_space();
4506                        self.write_keyword("FETCH NEXT");
4507                        self.write_space();
4508                        self.write_limit_expr(&limit.this)?;
4509                        self.write_space();
4510                        self.write_keyword("ROWS ONLY");
4511                    }
4512                } else {
4513                    self.write_keyword("OFFSET");
4514                    self.write_space();
4515                    self.write_limit_expr(&offset.this)?;
4516                    // Output ROWS keyword if it was in the original SQL
4517                    if offset.rows == Some(true) {
4518                        self.write_space();
4519                        self.write_keyword("ROWS");
4520                    }
4521                }
4522            }
4523        }
4524
4525        // ClickHouse LIMIT BY clause (after LIMIT/OFFSET)
4526        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
4527            if let Some(limit_by) = &select.limit_by {
4528                if !limit_by.is_empty() {
4529                    self.write_space();
4530                    self.write_keyword("BY");
4531                    self.write_space();
4532                    for (i, expr) in limit_by.iter().enumerate() {
4533                        if i > 0 {
4534                            self.write(", ");
4535                        }
4536                        self.generate_expression(expr)?;
4537                    }
4538                }
4539            }
4540        }
4541
4542        // ClickHouse SETTINGS and FORMAT modifiers (after LIMIT/OFFSET)
4543        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
4544            if let Some(settings) = &select.settings {
4545                if self.config.pretty {
4546                    self.write_newline();
4547                    self.write_indent();
4548                } else {
4549                    self.write_space();
4550                }
4551                self.write_keyword("SETTINGS");
4552                self.write_space();
4553                for (i, expr) in settings.iter().enumerate() {
4554                    if i > 0 {
4555                        self.write(", ");
4556                    }
4557                    self.generate_expression(expr)?;
4558                }
4559            }
4560
4561            if let Some(format_expr) = &select.format {
4562                if self.config.pretty {
4563                    self.write_newline();
4564                    self.write_indent();
4565                } else {
4566                    self.write_space();
4567                }
4568                self.write_keyword("FORMAT");
4569                self.write_space();
4570                self.generate_expression(format_expr)?;
4571            }
4572        }
4573
4574        // FETCH FIRST/NEXT
4575        if let Some(fetch) = &select.fetch {
4576            // Check if we already emitted LIMIT from FETCH before OFFSET
4577            let fetch_already_as_limit = select.offset.is_some()
4578                && !fetch.percent
4579                && !fetch.with_ties
4580                && fetch.count.is_some()
4581                && matches!(
4582                    self.config.dialect,
4583                    Some(DialectType::Spark)
4584                        | Some(DialectType::Hive)
4585                        | Some(DialectType::DuckDB)
4586                        | Some(DialectType::SQLite)
4587                        | Some(DialectType::MySQL)
4588                        | Some(DialectType::BigQuery)
4589                        | Some(DialectType::Databricks)
4590                        | Some(DialectType::StarRocks)
4591                        | Some(DialectType::Doris)
4592                        | Some(DialectType::Athena)
4593                        | Some(DialectType::ClickHouse)
4594                        | Some(DialectType::Redshift)
4595                );
4596
4597            if fetch_already_as_limit {
4598                // Already emitted as LIMIT before OFFSET, skip
4599            } else {
4600                if self.config.pretty {
4601                    self.write_newline();
4602                    self.write_indent();
4603                } else {
4604                    self.write_space();
4605                }
4606
4607                // Convert FETCH to LIMIT for dialects that prefer LIMIT syntax
4608                let use_limit = !fetch.percent
4609                    && !fetch.with_ties
4610                    && fetch.count.is_some()
4611                    && matches!(
4612                        self.config.dialect,
4613                        Some(DialectType::Spark)
4614                            | Some(DialectType::Hive)
4615                            | Some(DialectType::DuckDB)
4616                            | Some(DialectType::SQLite)
4617                            | Some(DialectType::MySQL)
4618                            | Some(DialectType::BigQuery)
4619                            | Some(DialectType::Databricks)
4620                            | Some(DialectType::StarRocks)
4621                            | Some(DialectType::Doris)
4622                            | Some(DialectType::Athena)
4623                            | Some(DialectType::ClickHouse)
4624                            | Some(DialectType::Redshift)
4625                    );
4626
4627                if use_limit {
4628                    self.write_keyword("LIMIT");
4629                    self.write_space();
4630                    self.generate_expression(fetch.count.as_ref().unwrap())?;
4631                } else {
4632                    self.write_keyword("FETCH");
4633                    self.write_space();
4634                    self.write_keyword(&fetch.direction);
4635                    if let Some(ref count) = fetch.count {
4636                        self.write_space();
4637                        self.generate_expression(count)?;
4638                    }
4639                    if fetch.percent {
4640                        self.write_space();
4641                        self.write_keyword("PERCENT");
4642                    }
4643                    if fetch.rows {
4644                        self.write_space();
4645                        self.write_keyword("ROWS");
4646                    }
4647                    if fetch.with_ties {
4648                        self.write_space();
4649                        self.write_keyword("WITH TIES");
4650                    } else {
4651                        self.write_space();
4652                        self.write_keyword("ONLY");
4653                    }
4654                }
4655            } // close fetch_already_as_limit else
4656        }
4657
4658        // SAMPLE / TABLESAMPLE
4659        if let Some(sample) = &select.sample {
4660            use crate::dialects::DialectType;
4661            if self.config.pretty {
4662                self.write_newline();
4663            } else {
4664                self.write_space();
4665            }
4666
4667            if sample.is_using_sample {
4668                // DuckDB USING SAMPLE: METHOD (size UNIT) [REPEATABLE (seed)]
4669                self.write_keyword("USING SAMPLE");
4670                self.generate_sample_body(sample)?;
4671            } else {
4672                self.write_keyword("TABLESAMPLE");
4673
4674                // Snowflake defaults to BERNOULLI when no explicit method is given
4675                let snowflake_bernoulli =
4676                    matches!(self.config.dialect, Some(DialectType::Snowflake))
4677                        && !sample.explicit_method;
4678                if snowflake_bernoulli {
4679                    self.write_space();
4680                    self.write_keyword("BERNOULLI");
4681                }
4682
4683                // Handle BUCKET sampling: TABLESAMPLE (BUCKET 1 OUT OF 5 ON x)
4684                if matches!(sample.method, SampleMethod::Bucket) {
4685                    self.write_space();
4686                    self.write("(");
4687                    self.write_keyword("BUCKET");
4688                    self.write_space();
4689                    if let Some(ref num) = sample.bucket_numerator {
4690                        self.generate_expression(num)?;
4691                    }
4692                    self.write_space();
4693                    self.write_keyword("OUT OF");
4694                    self.write_space();
4695                    if let Some(ref denom) = sample.bucket_denominator {
4696                        self.generate_expression(denom)?;
4697                    }
4698                    if let Some(ref field) = sample.bucket_field {
4699                        self.write_space();
4700                        self.write_keyword("ON");
4701                        self.write_space();
4702                        self.generate_expression(field)?;
4703                    }
4704                    self.write(")");
4705                } else if sample.unit_after_size {
4706                    // Syntax: TABLESAMPLE [METHOD] (size ROWS) or TABLESAMPLE [METHOD] (size PERCENT)
4707                    if sample.explicit_method && sample.method_before_size {
4708                        self.write_space();
4709                        match sample.method {
4710                            SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
4711                            SampleMethod::System => self.write_keyword("SYSTEM"),
4712                            SampleMethod::Block => self.write_keyword("BLOCK"),
4713                            SampleMethod::Row => self.write_keyword("ROW"),
4714                            SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
4715                            _ => {}
4716                        }
4717                    }
4718                    self.write(" (");
4719                    self.generate_expression(&sample.size)?;
4720                    self.write_space();
4721                    match sample.method {
4722                        SampleMethod::Percent => self.write_keyword("PERCENT"),
4723                        SampleMethod::Row => self.write_keyword("ROWS"),
4724                        SampleMethod::Reservoir => self.write_keyword("ROWS"),
4725                        _ => {
4726                            self.write_keyword("PERCENT");
4727                        }
4728                    }
4729                    self.write(")");
4730                } else {
4731                    // Syntax: TABLESAMPLE METHOD (size)
4732                    self.write_space();
4733                    match sample.method {
4734                        SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
4735                        SampleMethod::System => self.write_keyword("SYSTEM"),
4736                        SampleMethod::Block => self.write_keyword("BLOCK"),
4737                        SampleMethod::Row => self.write_keyword("ROW"),
4738                        SampleMethod::Percent => self.write_keyword("BERNOULLI"),
4739                        SampleMethod::Bucket => {}
4740                        SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
4741                    }
4742                    self.write(" (");
4743                    self.generate_expression(&sample.size)?;
4744                    if matches!(sample.method, SampleMethod::Percent) {
4745                        self.write_space();
4746                        self.write_keyword("PERCENT");
4747                    }
4748                    self.write(")");
4749                }
4750            }
4751
4752            if let Some(seed) = &sample.seed {
4753                self.write_space();
4754                // Databricks/Spark use REPEATABLE, not SEED
4755                let use_seed = sample.use_seed_keyword
4756                    && !matches!(
4757                        self.config.dialect,
4758                        Some(crate::dialects::DialectType::Databricks)
4759                            | Some(crate::dialects::DialectType::Spark)
4760                    );
4761                if use_seed {
4762                    self.write_keyword("SEED");
4763                } else {
4764                    self.write_keyword("REPEATABLE");
4765                }
4766                self.write(" (");
4767                self.generate_expression(seed)?;
4768                self.write(")");
4769            }
4770        }
4771
4772        // FOR UPDATE/SHARE locks
4773        // Skip locking clauses for dialects that don't support them
4774        if self.config.locking_reads_supported {
4775            for lock in &select.locks {
4776                if self.config.pretty {
4777                    self.write_newline();
4778                    self.write_indent();
4779                } else {
4780                    self.write_space();
4781                }
4782                self.generate_lock(lock)?;
4783            }
4784        }
4785
4786        // FOR XML clause (T-SQL)
4787        if !select.for_xml.is_empty() {
4788            if self.config.pretty {
4789                self.write_newline();
4790                self.write_indent();
4791            } else {
4792                self.write_space();
4793            }
4794            self.write_keyword("FOR XML");
4795            for (i, opt) in select.for_xml.iter().enumerate() {
4796                if self.config.pretty {
4797                    if i > 0 {
4798                        self.write(",");
4799                    }
4800                    self.write_newline();
4801                    self.write_indent();
4802                    self.write("  "); // extra indent for options
4803                } else {
4804                    if i > 0 {
4805                        self.write(",");
4806                    }
4807                    self.write_space();
4808                }
4809                self.generate_for_xml_option(opt)?;
4810            }
4811        }
4812
4813        // TSQL: OPTION clause
4814        if let Some(ref option) = select.option {
4815            if matches!(
4816                self.config.dialect,
4817                Some(crate::dialects::DialectType::TSQL)
4818                    | Some(crate::dialects::DialectType::Fabric)
4819            ) {
4820                self.write_space();
4821                self.write(option);
4822            }
4823        }
4824
4825        Ok(())
4826    }
4827
4828    /// Generate a single FOR XML option
4829    fn generate_for_xml_option(&mut self, opt: &Expression) -> Result<()> {
4830        match opt {
4831            Expression::QueryOption(qo) => {
4832                // Extract the option name from Var
4833                if let Expression::Var(var) = &*qo.this {
4834                    self.write(&var.this);
4835                } else {
4836                    self.generate_expression(&qo.this)?;
4837                }
4838                // If there's an expression (like PATH('element')), output it in parens
4839                if let Some(expr) = &qo.expression {
4840                    self.write("(");
4841                    self.generate_expression(expr)?;
4842                    self.write(")");
4843                }
4844            }
4845            _ => {
4846                self.generate_expression(opt)?;
4847            }
4848        }
4849        Ok(())
4850    }
4851
4852    fn generate_with(&mut self, with: &With) -> Result<()> {
4853        use crate::dialects::DialectType;
4854
4855        // Output leading comments before WITH
4856        for comment in &with.leading_comments {
4857            self.write_formatted_comment(comment);
4858            self.write(" ");
4859        }
4860        self.write_keyword("WITH");
4861        if with.recursive && self.config.cte_recursive_keyword_required {
4862            self.write_space();
4863            self.write_keyword("RECURSIVE");
4864        }
4865        self.write_space();
4866
4867        // BigQuery doesn't support column aliases in CTE definitions
4868        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
4869
4870        for (i, cte) in with.ctes.iter().enumerate() {
4871            if i > 0 {
4872                self.write(",");
4873                if self.config.pretty {
4874                    self.write_space();
4875                } else {
4876                    self.write(" ");
4877                }
4878            }
4879            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !cte.alias_first {
4880                self.generate_expression(&cte.this)?;
4881                self.write_space();
4882                self.write_keyword("AS");
4883                self.write_space();
4884                self.generate_identifier(&cte.alias)?;
4885                continue;
4886            }
4887            self.generate_identifier(&cte.alias)?;
4888            // Output CTE comments after alias name, before AS
4889            for comment in &cte.comments {
4890                self.write_space();
4891                self.write_formatted_comment(comment);
4892            }
4893            if !cte.columns.is_empty() && !skip_cte_columns {
4894                self.write("(");
4895                for (j, col) in cte.columns.iter().enumerate() {
4896                    if j > 0 {
4897                        self.write(", ");
4898                    }
4899                    self.generate_identifier(col)?;
4900                }
4901                self.write(")");
4902            }
4903            // USING KEY (columns) for DuckDB recursive CTEs
4904            if !cte.key_expressions.is_empty() {
4905                self.write_space();
4906                self.write_keyword("USING KEY");
4907                self.write(" (");
4908                for (i, key) in cte.key_expressions.iter().enumerate() {
4909                    if i > 0 {
4910                        self.write(", ");
4911                    }
4912                    self.generate_identifier(key)?;
4913                }
4914                self.write(")");
4915            }
4916            self.write_space();
4917            self.write_keyword("AS");
4918            // MATERIALIZED / NOT MATERIALIZED
4919            if let Some(materialized) = cte.materialized {
4920                self.write_space();
4921                if materialized {
4922                    self.write_keyword("MATERIALIZED");
4923                } else {
4924                    self.write_keyword("NOT MATERIALIZED");
4925                }
4926            }
4927            self.write(" (");
4928            if self.config.pretty {
4929                self.write_newline();
4930                self.indent_level += 1;
4931                self.write_indent();
4932            }
4933            // For Spark/Databricks, VALUES in a CTE must be wrapped with SELECT * FROM
4934            // e.g., WITH t AS (VALUES ('foo_val') AS t(foo1)) -> WITH t AS (SELECT * FROM VALUES ('foo_val') AS t(foo1))
4935            let wrap_values_in_select = matches!(
4936                self.config.dialect,
4937                Some(DialectType::Spark) | Some(DialectType::Databricks)
4938            ) && matches!(&cte.this, Expression::Values(_));
4939
4940            if wrap_values_in_select {
4941                self.write_keyword("SELECT");
4942                self.write(" * ");
4943                self.write_keyword("FROM");
4944                self.write_space();
4945            }
4946            self.generate_expression(&cte.this)?;
4947            if self.config.pretty {
4948                self.write_newline();
4949                self.indent_level -= 1;
4950                self.write_indent();
4951            }
4952            self.write(")");
4953        }
4954
4955        // Generate SEARCH/CYCLE clause if present
4956        if let Some(search) = &with.search {
4957            self.write_space();
4958            self.generate_expression(search)?;
4959        }
4960
4961        Ok(())
4962    }
4963
4964    /// Generate joins with proper nesting structure for pretty printing.
4965    /// Deferred-condition joins "own" the non-deferred joins that follow them
4966    /// within the same nesting_group.
4967    fn generate_joins_with_nesting(&mut self, joins: &[Join]) -> Result<()> {
4968        let mut i = 0;
4969        while i < joins.len() {
4970            if joins[i].deferred_condition {
4971                let parent_group = joins[i].nesting_group;
4972
4973                // This join owns the following non-deferred joins in the same nesting_group
4974                // First output the join keyword and table (without condition)
4975                self.generate_join_without_condition(&joins[i])?;
4976
4977                // Find the range of child joins: same nesting_group and not deferred
4978                let child_start = i + 1;
4979                let mut child_end = child_start;
4980                while child_end < joins.len()
4981                    && !joins[child_end].deferred_condition
4982                    && joins[child_end].nesting_group == parent_group
4983                {
4984                    child_end += 1;
4985                }
4986
4987                // Output child joins with extra indentation
4988                if child_start < child_end {
4989                    self.indent_level += 1;
4990                    for j in child_start..child_end {
4991                        self.generate_join(&joins[j])?;
4992                    }
4993                    self.indent_level -= 1;
4994                }
4995
4996                // Output the deferred condition at the parent level
4997                self.generate_join_condition(&joins[i])?;
4998
4999                i = child_end;
5000            } else {
5001                // Regular join (no nesting)
5002                self.generate_join(&joins[i])?;
5003                i += 1;
5004            }
5005        }
5006        Ok(())
5007    }
5008
5009    /// Generate a join's keyword and table reference, but not its ON/USING condition.
5010    /// Used for deferred-condition joins where the condition is output after child joins.
5011    fn generate_join_without_condition(&mut self, join: &Join) -> Result<()> {
5012        // Save and temporarily clear the condition to prevent generate_join from outputting it
5013        // We achieve this by creating a modified copy
5014        let mut join_copy = join.clone();
5015        join_copy.on = None;
5016        join_copy.using = Vec::new();
5017        join_copy.deferred_condition = false;
5018        self.generate_join(&join_copy)
5019    }
5020
5021    fn generate_join(&mut self, join: &Join) -> Result<()> {
5022        // Implicit (comma) joins: output as ", table" instead of "CROSS JOIN table"
5023        if join.kind == JoinKind::Implicit {
5024            self.write(",");
5025            if self.config.pretty {
5026                self.write_newline();
5027                self.write_indent();
5028            } else {
5029                self.write_space();
5030            }
5031            self.generate_expression(&join.this)?;
5032            return Ok(());
5033        }
5034
5035        if self.config.pretty {
5036            self.write_newline();
5037            self.write_indent();
5038        } else {
5039            self.write_space();
5040        }
5041
5042        // Helper: format hint suffix (e.g., " LOOP" or "")
5043        // Only include join hints for dialects that support them
5044        let hint_str = if self.config.join_hints {
5045            join.join_hint
5046                .as_ref()
5047                .map(|h| format!(" {}", h))
5048                .unwrap_or_default()
5049        } else {
5050            String::new()
5051        };
5052
5053        let clickhouse_join_keyword =
5054            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
5055                if let Some(hint) = &join.join_hint {
5056                    let mut global = false;
5057                    let mut strictness: Option<&'static str> = None;
5058                    for part in hint.split_whitespace() {
5059                        match part.to_uppercase().as_str() {
5060                            "GLOBAL" => global = true,
5061                            "ANY" => strictness = Some("ANY"),
5062                            "ASOF" => strictness = Some("ASOF"),
5063                            "SEMI" => strictness = Some("SEMI"),
5064                            "ANTI" => strictness = Some("ANTI"),
5065                            _ => {}
5066                        }
5067                    }
5068
5069                    if global || strictness.is_some() {
5070                        let join_type = match join.kind {
5071                            JoinKind::Left => {
5072                                if join.use_outer_keyword {
5073                                    "LEFT OUTER"
5074                                } else if join.use_inner_keyword {
5075                                    "LEFT INNER"
5076                                } else {
5077                                    "LEFT"
5078                                }
5079                            }
5080                            JoinKind::Right => {
5081                                if join.use_outer_keyword {
5082                                    "RIGHT OUTER"
5083                                } else if join.use_inner_keyword {
5084                                    "RIGHT INNER"
5085                                } else {
5086                                    "RIGHT"
5087                                }
5088                            }
5089                            JoinKind::Full => {
5090                                if join.use_outer_keyword {
5091                                    "FULL OUTER"
5092                                } else {
5093                                    "FULL"
5094                                }
5095                            }
5096                            JoinKind::Inner => {
5097                                if join.use_inner_keyword {
5098                                    "INNER"
5099                                } else {
5100                                    ""
5101                                }
5102                            }
5103                            _ => "",
5104                        };
5105
5106                        let mut parts = Vec::new();
5107                        if global {
5108                            parts.push("GLOBAL");
5109                        }
5110                        if !join_type.is_empty() {
5111                            parts.push(join_type);
5112                        }
5113                        if let Some(strict) = strictness {
5114                            parts.push(strict);
5115                        }
5116                        parts.push("JOIN");
5117                        Some(parts.join(" "))
5118                    } else {
5119                        None
5120                    }
5121                } else {
5122                    None
5123                }
5124            } else {
5125                None
5126            };
5127
5128        // Output any comments associated with this join
5129        // In pretty mode, comments go on their own line before the join keyword
5130        // In non-pretty mode, comments go inline before the join keyword
5131        if !join.comments.is_empty() {
5132            if self.config.pretty {
5133                // In pretty mode, go back before the newline+indent we just wrote
5134                // and output comments on their own lines
5135                // We need to output comments BEFORE the join keyword on separate lines
5136                // Trim the trailing newline+indent we already wrote
5137                let trimmed = self.output.trim_end().len();
5138                self.output.truncate(trimmed);
5139                for comment in &join.comments {
5140                    self.write_newline();
5141                    self.write_indent();
5142                    self.write_formatted_comment(comment);
5143                }
5144                self.write_newline();
5145                self.write_indent();
5146            } else {
5147                for comment in &join.comments {
5148                    self.write_formatted_comment(comment);
5149                    self.write_space();
5150                }
5151            }
5152        }
5153
5154        let directed_str = if join.directed { " DIRECTED" } else { "" };
5155
5156        if let Some(keyword) = clickhouse_join_keyword {
5157            self.write_keyword(&keyword);
5158        } else {
5159            match join.kind {
5160                JoinKind::Inner => {
5161                    if join.use_inner_keyword {
5162                        self.write_keyword(&format!("INNER{}{} JOIN", hint_str, directed_str));
5163                    } else {
5164                        self.write_keyword(&format!(
5165                            "{}{}JOIN",
5166                            if hint_str.is_empty() {
5167                                String::new()
5168                            } else {
5169                                format!("{} ", hint_str.trim())
5170                            },
5171                            if directed_str.is_empty() {
5172                                ""
5173                            } else {
5174                                "DIRECTED "
5175                            }
5176                        ));
5177                    }
5178                }
5179                JoinKind::Left => {
5180                    if join.use_outer_keyword {
5181                        self.write_keyword(&format!("LEFT OUTER{}{} JOIN", hint_str, directed_str));
5182                    } else if join.use_inner_keyword {
5183                        self.write_keyword(&format!("LEFT INNER{}{} JOIN", hint_str, directed_str));
5184                    } else {
5185                        self.write_keyword(&format!("LEFT{}{} JOIN", hint_str, directed_str));
5186                    }
5187                }
5188                JoinKind::Right => {
5189                    if join.use_outer_keyword {
5190                        self.write_keyword(&format!(
5191                            "RIGHT OUTER{}{} JOIN",
5192                            hint_str, directed_str
5193                        ));
5194                    } else if join.use_inner_keyword {
5195                        self.write_keyword(&format!(
5196                            "RIGHT INNER{}{} JOIN",
5197                            hint_str, directed_str
5198                        ));
5199                    } else {
5200                        self.write_keyword(&format!("RIGHT{}{} JOIN", hint_str, directed_str));
5201                    }
5202                }
5203                JoinKind::Full => {
5204                    if join.use_outer_keyword {
5205                        self.write_keyword(&format!("FULL OUTER{}{} JOIN", hint_str, directed_str));
5206                    } else {
5207                        self.write_keyword(&format!("FULL{}{} JOIN", hint_str, directed_str));
5208                    }
5209                }
5210                JoinKind::Outer => self.write_keyword(&format!("OUTER{} JOIN", directed_str)),
5211                JoinKind::Cross => self.write_keyword(&format!("CROSS{} JOIN", directed_str)),
5212                JoinKind::Natural => {
5213                    if join.use_inner_keyword {
5214                        self.write_keyword(&format!("NATURAL INNER{} JOIN", directed_str));
5215                    } else {
5216                        self.write_keyword(&format!("NATURAL{} JOIN", directed_str));
5217                    }
5218                }
5219                JoinKind::NaturalLeft => {
5220                    if join.use_outer_keyword {
5221                        self.write_keyword(&format!("NATURAL LEFT OUTER{} JOIN", directed_str));
5222                    } else {
5223                        self.write_keyword(&format!("NATURAL LEFT{} JOIN", directed_str));
5224                    }
5225                }
5226                JoinKind::NaturalRight => {
5227                    if join.use_outer_keyword {
5228                        self.write_keyword(&format!("NATURAL RIGHT OUTER{} JOIN", directed_str));
5229                    } else {
5230                        self.write_keyword(&format!("NATURAL RIGHT{} JOIN", directed_str));
5231                    }
5232                }
5233                JoinKind::NaturalFull => {
5234                    if join.use_outer_keyword {
5235                        self.write_keyword(&format!("NATURAL FULL OUTER{} JOIN", directed_str));
5236                    } else {
5237                        self.write_keyword(&format!("NATURAL FULL{} JOIN", directed_str));
5238                    }
5239                }
5240                JoinKind::Semi => self.write_keyword("SEMI JOIN"),
5241                JoinKind::Anti => self.write_keyword("ANTI JOIN"),
5242                JoinKind::LeftSemi => self.write_keyword("LEFT SEMI JOIN"),
5243                JoinKind::LeftAnti => self.write_keyword("LEFT ANTI JOIN"),
5244                JoinKind::RightSemi => self.write_keyword("RIGHT SEMI JOIN"),
5245                JoinKind::RightAnti => self.write_keyword("RIGHT ANTI JOIN"),
5246                JoinKind::CrossApply => {
5247                    // CROSS APPLY -> INNER JOIN LATERAL for non-TSQL dialects
5248                    if matches!(self.config.dialect, Some(DialectType::TSQL) | None) {
5249                        self.write_keyword("CROSS APPLY");
5250                    } else {
5251                        self.write_keyword("INNER JOIN LATERAL");
5252                    }
5253                }
5254                JoinKind::OuterApply => {
5255                    // OUTER APPLY -> LEFT JOIN LATERAL for non-TSQL dialects
5256                    if matches!(self.config.dialect, Some(DialectType::TSQL) | None) {
5257                        self.write_keyword("OUTER APPLY");
5258                    } else {
5259                        self.write_keyword("LEFT JOIN LATERAL");
5260                    }
5261                }
5262                JoinKind::AsOf => self.write_keyword("ASOF JOIN"),
5263                JoinKind::AsOfLeft => {
5264                    if join.use_outer_keyword {
5265                        self.write_keyword("ASOF LEFT OUTER JOIN");
5266                    } else {
5267                        self.write_keyword("ASOF LEFT JOIN");
5268                    }
5269                }
5270                JoinKind::AsOfRight => {
5271                    if join.use_outer_keyword {
5272                        self.write_keyword("ASOF RIGHT OUTER JOIN");
5273                    } else {
5274                        self.write_keyword("ASOF RIGHT JOIN");
5275                    }
5276                }
5277                JoinKind::Lateral => self.write_keyword("LATERAL JOIN"),
5278                JoinKind::LeftLateral => {
5279                    if join.use_outer_keyword {
5280                        self.write_keyword("LEFT OUTER LATERAL JOIN");
5281                    } else {
5282                        self.write_keyword("LEFT LATERAL JOIN");
5283                    }
5284                }
5285                JoinKind::Straight => self.write_keyword("STRAIGHT_JOIN"),
5286                JoinKind::Implicit => {
5287                    // BigQuery, Hive, Spark, and Databricks prefer explicit CROSS JOIN over comma syntax
5288                    // But only when source is the same dialect (identity) or source is another CROSS JOIN dialect
5289                    // When source is Generic, keep commas (Python sqlglot: parser marks joins, not generator)
5290                    use crate::dialects::DialectType;
5291                    let is_cj_dialect = matches!(
5292                        self.config.dialect,
5293                        Some(DialectType::BigQuery)
5294                            | Some(DialectType::Hive)
5295                            | Some(DialectType::Spark)
5296                            | Some(DialectType::Databricks)
5297                    );
5298                    let source_is_same = self.config.source_dialect.is_some()
5299                        && self.config.source_dialect == self.config.dialect;
5300                    let source_is_cj = matches!(
5301                        self.config.source_dialect,
5302                        Some(DialectType::BigQuery)
5303                            | Some(DialectType::Hive)
5304                            | Some(DialectType::Spark)
5305                            | Some(DialectType::Databricks)
5306                    );
5307                    if is_cj_dialect
5308                        && (source_is_same || source_is_cj || self.config.source_dialect.is_none())
5309                    {
5310                        self.write_keyword("CROSS JOIN");
5311                    } else {
5312                        // Implicit join uses comma: FROM a, b
5313                        // We already wrote a space before the match, so replace with comma
5314                        // by removing trailing space and writing ", "
5315                        self.output.truncate(self.output.trim_end().len());
5316                        self.write(",");
5317                    }
5318                }
5319                JoinKind::Array => self.write_keyword("ARRAY JOIN"),
5320                JoinKind::LeftArray => self.write_keyword("LEFT ARRAY JOIN"),
5321                JoinKind::Paste => self.write_keyword("PASTE JOIN"),
5322            }
5323        }
5324
5325        // ARRAY JOIN items need comma-separated output (Tuple holds multiple items)
5326        if matches!(join.kind, JoinKind::Array | JoinKind::LeftArray) {
5327            self.write_space();
5328            match &join.this {
5329                Expression::Tuple(t) => {
5330                    for (i, item) in t.expressions.iter().enumerate() {
5331                        if i > 0 {
5332                            self.write(", ");
5333                        }
5334                        self.generate_expression(item)?;
5335                    }
5336                }
5337                other => {
5338                    self.generate_expression(other)?;
5339                }
5340            }
5341        } else {
5342            self.write_space();
5343            self.generate_expression(&join.this)?;
5344        }
5345
5346        // Only output MATCH_CONDITION/ON/USING inline if the condition wasn't deferred
5347        if !join.deferred_condition {
5348            // Output MATCH_CONDITION first (Snowflake ASOF JOIN)
5349            if let Some(match_cond) = &join.match_condition {
5350                self.write_space();
5351                self.write_keyword("MATCH_CONDITION");
5352                self.write(" (");
5353                self.generate_expression(match_cond)?;
5354                self.write(")");
5355            }
5356
5357            if let Some(on) = &join.on {
5358                if self.config.pretty {
5359                    self.write_newline();
5360                    self.indent_level += 1;
5361                    self.write_indent();
5362                    self.write_keyword("ON");
5363                    self.write_space();
5364                    self.generate_join_on_condition(on)?;
5365                    self.indent_level -= 1;
5366                } else {
5367                    self.write_space();
5368                    self.write_keyword("ON");
5369                    self.write_space();
5370                    self.generate_expression(on)?;
5371                }
5372            }
5373
5374            if !join.using.is_empty() {
5375                if self.config.pretty {
5376                    self.write_newline();
5377                    self.indent_level += 1;
5378                    self.write_indent();
5379                    self.write_keyword("USING");
5380                    self.write(" (");
5381                    for (i, col) in join.using.iter().enumerate() {
5382                        if i > 0 {
5383                            self.write(", ");
5384                        }
5385                        self.generate_identifier(col)?;
5386                    }
5387                    self.write(")");
5388                    self.indent_level -= 1;
5389                } else {
5390                    self.write_space();
5391                    self.write_keyword("USING");
5392                    self.write(" (");
5393                    for (i, col) in join.using.iter().enumerate() {
5394                        if i > 0 {
5395                            self.write(", ");
5396                        }
5397                        self.generate_identifier(col)?;
5398                    }
5399                    self.write(")");
5400                }
5401            }
5402        }
5403
5404        // Generate PIVOT/UNPIVOT expressions that follow this join
5405        for pivot in &join.pivots {
5406            self.write_space();
5407            self.generate_expression(pivot)?;
5408        }
5409
5410        Ok(())
5411    }
5412
5413    /// Generate just the ON/USING/MATCH_CONDITION for a join (used for deferred conditions)
5414    fn generate_join_condition(&mut self, join: &Join) -> Result<()> {
5415        // Generate MATCH_CONDITION first (Snowflake ASOF JOIN)
5416        if let Some(match_cond) = &join.match_condition {
5417            self.write_space();
5418            self.write_keyword("MATCH_CONDITION");
5419            self.write(" (");
5420            self.generate_expression(match_cond)?;
5421            self.write(")");
5422        }
5423
5424        if let Some(on) = &join.on {
5425            if self.config.pretty {
5426                self.write_newline();
5427                self.indent_level += 1;
5428                self.write_indent();
5429                self.write_keyword("ON");
5430                self.write_space();
5431                // In pretty mode, split AND conditions onto separate lines
5432                self.generate_join_on_condition(on)?;
5433                self.indent_level -= 1;
5434            } else {
5435                self.write_space();
5436                self.write_keyword("ON");
5437                self.write_space();
5438                self.generate_expression(on)?;
5439            }
5440        }
5441
5442        if !join.using.is_empty() {
5443            if self.config.pretty {
5444                self.write_newline();
5445                self.indent_level += 1;
5446                self.write_indent();
5447                self.write_keyword("USING");
5448                self.write(" (");
5449                for (i, col) in join.using.iter().enumerate() {
5450                    if i > 0 {
5451                        self.write(", ");
5452                    }
5453                    self.generate_identifier(col)?;
5454                }
5455                self.write(")");
5456                self.indent_level -= 1;
5457            } else {
5458                self.write_space();
5459                self.write_keyword("USING");
5460                self.write(" (");
5461                for (i, col) in join.using.iter().enumerate() {
5462                    if i > 0 {
5463                        self.write(", ");
5464                    }
5465                    self.generate_identifier(col)?;
5466                }
5467                self.write(")");
5468            }
5469        }
5470
5471        // Generate PIVOT/UNPIVOT expressions that follow this join (for deferred conditions)
5472        for pivot in &join.pivots {
5473            self.write_space();
5474            self.generate_expression(pivot)?;
5475        }
5476
5477        Ok(())
5478    }
5479
5480    /// Generate JOIN ON condition with AND clauses on separate lines in pretty mode
5481    fn generate_join_on_condition(&mut self, expr: &Expression) -> Result<()> {
5482        // If it's an AND expression, split each condition onto a new line
5483        if let Expression::And(and_op) = expr {
5484            // Generate left side (might be another AND)
5485            self.generate_join_on_condition(&and_op.left)?;
5486            // AND on new line
5487            self.write_newline();
5488            self.write_indent();
5489            self.write_keyword("AND");
5490            self.write_space();
5491            // Generate right side (should be a single condition)
5492            self.generate_expression(&and_op.right)?;
5493        } else {
5494            // Base case: single condition
5495            self.generate_expression(expr)?;
5496        }
5497        Ok(())
5498    }
5499
5500    fn generate_joined_table(&mut self, jt: &JoinedTable) -> Result<()> {
5501        // Parenthesized join: (tbl1 CROSS JOIN tbl2)
5502        self.write("(");
5503        self.generate_expression(&jt.left)?;
5504
5505        // Generate all joins
5506        for join in &jt.joins {
5507            self.generate_join(join)?;
5508        }
5509
5510        // Generate LATERAL VIEW clauses (Hive/Spark)
5511        for lv in &jt.lateral_views {
5512            self.generate_lateral_view(lv)?;
5513        }
5514
5515        self.write(")");
5516
5517        // Alias
5518        if let Some(alias) = &jt.alias {
5519            self.write_space();
5520            self.write_keyword("AS");
5521            self.write_space();
5522            self.generate_identifier(alias)?;
5523        }
5524
5525        Ok(())
5526    }
5527
5528    fn generate_lateral_view(&mut self, lv: &LateralView) -> Result<()> {
5529        use crate::dialects::DialectType;
5530
5531        if self.config.pretty {
5532            self.write_newline();
5533            self.write_indent();
5534        } else {
5535            self.write_space();
5536        }
5537
5538        // For Hive/Spark/Databricks (or no dialect specified), output native LATERAL VIEW syntax
5539        // For PostgreSQL and other specific dialects, convert to CROSS JOIN (LATERAL or UNNEST)
5540        let use_lateral_join = matches!(
5541            self.config.dialect,
5542            Some(DialectType::PostgreSQL)
5543                | Some(DialectType::DuckDB)
5544                | Some(DialectType::Snowflake)
5545                | Some(DialectType::TSQL)
5546                | Some(DialectType::Presto)
5547                | Some(DialectType::Trino)
5548                | Some(DialectType::Athena)
5549        );
5550
5551        // Check if target dialect should use UNNEST instead of EXPLODE
5552        let use_unnest = matches!(
5553            self.config.dialect,
5554            Some(DialectType::DuckDB)
5555                | Some(DialectType::Presto)
5556                | Some(DialectType::Trino)
5557                | Some(DialectType::Athena)
5558        );
5559
5560        // Check if we need POSEXPLODE -> UNNEST WITH ORDINALITY
5561        let (is_posexplode, func_args) = match &lv.this {
5562            Expression::Explode(uf) => {
5563                // Expression::Explode is the dedicated EXPLODE expression type
5564                (false, vec![uf.this.clone()])
5565            }
5566            Expression::Unnest(uf) => {
5567                let mut args = vec![uf.this.clone()];
5568                args.extend(uf.expressions.clone());
5569                (false, args)
5570            }
5571            Expression::Function(func) => {
5572                let name = func.name.to_uppercase();
5573                if name == "POSEXPLODE" || name == "POSEXPLODE_OUTER" {
5574                    (true, func.args.clone())
5575                } else if name == "EXPLODE" || name == "EXPLODE_OUTER" || name == "INLINE" {
5576                    (false, func.args.clone())
5577                } else {
5578                    (false, vec![])
5579                }
5580            }
5581            _ => (false, vec![]),
5582        };
5583
5584        if use_lateral_join {
5585            // Convert to CROSS JOIN for PostgreSQL-like dialects
5586            if lv.outer {
5587                self.write_keyword("LEFT JOIN LATERAL");
5588            } else {
5589                self.write_keyword("CROSS JOIN");
5590            }
5591            self.write_space();
5592
5593            if use_unnest && !func_args.is_empty() {
5594                // Convert EXPLODE(y) -> UNNEST(y), POSEXPLODE(y) -> UNNEST(y)
5595                // For DuckDB, also convert ARRAY(y) -> [y]
5596                let unnest_args = if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
5597                    // DuckDB: ARRAY(y) -> [y]
5598                    func_args
5599                        .iter()
5600                        .map(|a| {
5601                            if let Expression::Function(ref f) = a {
5602                                if f.name.to_uppercase() == "ARRAY" && f.args.len() == 1 {
5603                                    return Expression::ArrayFunc(Box::new(
5604                                        crate::expressions::ArrayConstructor {
5605                                            expressions: f.args.clone(),
5606                                            bracket_notation: true,
5607                                            use_list_keyword: false,
5608                                        },
5609                                    ));
5610                                }
5611                            }
5612                            a.clone()
5613                        })
5614                        .collect::<Vec<_>>()
5615                } else if matches!(
5616                    self.config.dialect,
5617                    Some(DialectType::Presto)
5618                        | Some(DialectType::Trino)
5619                        | Some(DialectType::Athena)
5620                ) {
5621                    // Presto: ARRAY(y) -> ARRAY[y]
5622                    func_args
5623                        .iter()
5624                        .map(|a| {
5625                            if let Expression::Function(ref f) = a {
5626                                if f.name.to_uppercase() == "ARRAY" && f.args.len() >= 1 {
5627                                    return Expression::ArrayFunc(Box::new(
5628                                        crate::expressions::ArrayConstructor {
5629                                            expressions: f.args.clone(),
5630                                            bracket_notation: true,
5631                                            use_list_keyword: false,
5632                                        },
5633                                    ));
5634                                }
5635                            }
5636                            a.clone()
5637                        })
5638                        .collect::<Vec<_>>()
5639                } else {
5640                    func_args
5641                };
5642
5643                // POSEXPLODE -> LATERAL (SELECT pos - 1 AS pos, col FROM UNNEST(y) WITH ORDINALITY AS t(col, pos))
5644                if is_posexplode {
5645                    self.write_keyword("LATERAL");
5646                    self.write(" (");
5647                    self.write_keyword("SELECT");
5648                    self.write_space();
5649
5650                    // Build the outer SELECT list: pos - 1 AS pos, then data columns
5651                    // column_aliases[0] is the position column, rest are data columns
5652                    let pos_alias = if !lv.column_aliases.is_empty() {
5653                        lv.column_aliases[0].clone()
5654                    } else {
5655                        Identifier::new("pos")
5656                    };
5657                    let data_aliases: Vec<Identifier> = if lv.column_aliases.len() > 1 {
5658                        lv.column_aliases[1..].to_vec()
5659                    } else {
5660                        vec![Identifier::new("col")]
5661                    };
5662
5663                    // pos - 1 AS pos
5664                    self.generate_identifier(&pos_alias)?;
5665                    self.write(" - 1");
5666                    self.write_space();
5667                    self.write_keyword("AS");
5668                    self.write_space();
5669                    self.generate_identifier(&pos_alias)?;
5670
5671                    // , col [, key, value ...]
5672                    for data_col in &data_aliases {
5673                        self.write(", ");
5674                        self.generate_identifier(data_col)?;
5675                    }
5676
5677                    self.write_space();
5678                    self.write_keyword("FROM");
5679                    self.write_space();
5680                    self.write_keyword("UNNEST");
5681                    self.write("(");
5682                    for (i, arg) in unnest_args.iter().enumerate() {
5683                        if i > 0 {
5684                            self.write(", ");
5685                        }
5686                        self.generate_expression(arg)?;
5687                    }
5688                    self.write(")");
5689                    self.write_space();
5690                    self.write_keyword("WITH ORDINALITY");
5691                    self.write_space();
5692                    self.write_keyword("AS");
5693                    self.write_space();
5694
5695                    // Inner alias: t(data_cols..., pos) - data columns first, pos last
5696                    let table_alias_ident = lv
5697                        .table_alias
5698                        .clone()
5699                        .unwrap_or_else(|| Identifier::new("t"));
5700                    self.generate_identifier(&table_alias_ident)?;
5701                    self.write("(");
5702                    for (i, data_col) in data_aliases.iter().enumerate() {
5703                        if i > 0 {
5704                            self.write(", ");
5705                        }
5706                        self.generate_identifier(data_col)?;
5707                    }
5708                    self.write(", ");
5709                    self.generate_identifier(&pos_alias)?;
5710                    self.write("))");
5711                } else {
5712                    self.write_keyword("UNNEST");
5713                    self.write("(");
5714                    for (i, arg) in unnest_args.iter().enumerate() {
5715                        if i > 0 {
5716                            self.write(", ");
5717                        }
5718                        self.generate_expression(arg)?;
5719                    }
5720                    self.write(")");
5721
5722                    // Add table and column aliases for non-POSEXPLODE
5723                    if let Some(alias) = &lv.table_alias {
5724                        self.write_space();
5725                        self.write_keyword("AS");
5726                        self.write_space();
5727                        self.generate_identifier(alias)?;
5728                        if !lv.column_aliases.is_empty() {
5729                            self.write("(");
5730                            for (i, col) in lv.column_aliases.iter().enumerate() {
5731                                if i > 0 {
5732                                    self.write(", ");
5733                                }
5734                                self.generate_identifier(col)?;
5735                            }
5736                            self.write(")");
5737                        }
5738                    } else if !lv.column_aliases.is_empty() {
5739                        self.write_space();
5740                        self.write_keyword("AS");
5741                        self.write(" t(");
5742                        for (i, col) in lv.column_aliases.iter().enumerate() {
5743                            if i > 0 {
5744                                self.write(", ");
5745                            }
5746                            self.generate_identifier(col)?;
5747                        }
5748                        self.write(")");
5749                    }
5750                }
5751            } else {
5752                // Not EXPLODE/POSEXPLODE or not using UNNEST, use LATERAL
5753                if !lv.outer {
5754                    self.write_keyword("LATERAL");
5755                    self.write_space();
5756                }
5757                self.generate_expression(&lv.this)?;
5758
5759                // Add table and column aliases
5760                if let Some(alias) = &lv.table_alias {
5761                    self.write_space();
5762                    self.write_keyword("AS");
5763                    self.write_space();
5764                    self.generate_identifier(alias)?;
5765                    if !lv.column_aliases.is_empty() {
5766                        self.write("(");
5767                        for (i, col) in lv.column_aliases.iter().enumerate() {
5768                            if i > 0 {
5769                                self.write(", ");
5770                            }
5771                            self.generate_identifier(col)?;
5772                        }
5773                        self.write(")");
5774                    }
5775                } else if !lv.column_aliases.is_empty() {
5776                    self.write_space();
5777                    self.write_keyword("AS");
5778                    self.write(" t(");
5779                    for (i, col) in lv.column_aliases.iter().enumerate() {
5780                        if i > 0 {
5781                            self.write(", ");
5782                        }
5783                        self.generate_identifier(col)?;
5784                    }
5785                    self.write(")");
5786                }
5787            }
5788
5789            // For LEFT JOIN LATERAL, need ON TRUE
5790            if lv.outer {
5791                self.write_space();
5792                self.write_keyword("ON TRUE");
5793            }
5794        } else {
5795            // Output native LATERAL VIEW syntax (Hive/Spark/Databricks or default)
5796            self.write_keyword("LATERAL VIEW");
5797            if lv.outer {
5798                self.write_space();
5799                self.write_keyword("OUTER");
5800            }
5801            if self.config.pretty {
5802                self.write_newline();
5803                self.write_indent();
5804            } else {
5805                self.write_space();
5806            }
5807            self.generate_expression(&lv.this)?;
5808
5809            // Table alias
5810            if let Some(alias) = &lv.table_alias {
5811                self.write_space();
5812                self.generate_identifier(alias)?;
5813            }
5814
5815            // Column aliases
5816            if !lv.column_aliases.is_empty() {
5817                self.write_space();
5818                self.write_keyword("AS");
5819                self.write_space();
5820                for (i, col) in lv.column_aliases.iter().enumerate() {
5821                    if i > 0 {
5822                        self.write(", ");
5823                    }
5824                    self.generate_identifier(col)?;
5825                }
5826            }
5827        }
5828
5829        Ok(())
5830    }
5831
5832    fn generate_union(&mut self, union: &Union) -> Result<()> {
5833        // WITH clause
5834        if let Some(with) = &union.with {
5835            self.generate_with(with)?;
5836            self.write_space();
5837        }
5838        self.generate_expression(&union.left)?;
5839        if self.config.pretty {
5840            self.write_newline();
5841            self.write_indent();
5842        } else {
5843            self.write_space();
5844        }
5845
5846        // BigQuery set operation modifiers: [side] [kind] UNION
5847        if let Some(side) = &union.side {
5848            self.write_keyword(side);
5849            self.write_space();
5850        }
5851        if let Some(kind) = &union.kind {
5852            self.write_keyword(kind);
5853            self.write_space();
5854        }
5855
5856        self.write_keyword("UNION");
5857        if union.all {
5858            self.write_space();
5859            self.write_keyword("ALL");
5860        } else if union.distinct {
5861            self.write_space();
5862            self.write_keyword("DISTINCT");
5863        }
5864
5865        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
5866        // DuckDB: BY NAME
5867        if union.corresponding || union.by_name {
5868            self.write_space();
5869            self.write_keyword("BY NAME");
5870        }
5871        if !union.on_columns.is_empty() {
5872            self.write_space();
5873            self.write_keyword("ON");
5874            self.write(" (");
5875            for (i, col) in union.on_columns.iter().enumerate() {
5876                if i > 0 {
5877                    self.write(", ");
5878                }
5879                self.generate_expression(col)?;
5880            }
5881            self.write(")");
5882        }
5883
5884        if self.config.pretty {
5885            self.write_newline();
5886            self.write_indent();
5887        } else {
5888            self.write_space();
5889        }
5890        self.generate_expression(&union.right)?;
5891        // ORDER BY, LIMIT, OFFSET for the set operation
5892        if let Some(order_by) = &union.order_by {
5893            if self.config.pretty {
5894                self.write_newline();
5895            } else {
5896                self.write_space();
5897            }
5898            self.write_keyword("ORDER BY");
5899            self.write_space();
5900            for (i, ordered) in order_by.expressions.iter().enumerate() {
5901                if i > 0 {
5902                    self.write(", ");
5903                }
5904                self.generate_ordered(ordered)?;
5905            }
5906        }
5907        if let Some(limit) = &union.limit {
5908            if self.config.pretty {
5909                self.write_newline();
5910            } else {
5911                self.write_space();
5912            }
5913            self.write_keyword("LIMIT");
5914            self.write_space();
5915            self.generate_expression(limit)?;
5916        }
5917        if let Some(offset) = &union.offset {
5918            if self.config.pretty {
5919                self.write_newline();
5920            } else {
5921                self.write_space();
5922            }
5923            self.write_keyword("OFFSET");
5924            self.write_space();
5925            self.generate_expression(offset)?;
5926        }
5927        // DISTRIBUTE BY (Hive/Spark)
5928        if let Some(distribute_by) = &union.distribute_by {
5929            self.write_space();
5930            self.write_keyword("DISTRIBUTE BY");
5931            self.write_space();
5932            for (i, expr) in distribute_by.expressions.iter().enumerate() {
5933                if i > 0 {
5934                    self.write(", ");
5935                }
5936                self.generate_expression(expr)?;
5937            }
5938        }
5939        // SORT BY (Hive/Spark)
5940        if let Some(sort_by) = &union.sort_by {
5941            self.write_space();
5942            self.write_keyword("SORT BY");
5943            self.write_space();
5944            for (i, ord) in sort_by.expressions.iter().enumerate() {
5945                if i > 0 {
5946                    self.write(", ");
5947                }
5948                self.generate_ordered(ord)?;
5949            }
5950        }
5951        // CLUSTER BY (Hive/Spark)
5952        if let Some(cluster_by) = &union.cluster_by {
5953            self.write_space();
5954            self.write_keyword("CLUSTER BY");
5955            self.write_space();
5956            for (i, ord) in cluster_by.expressions.iter().enumerate() {
5957                if i > 0 {
5958                    self.write(", ");
5959                }
5960                self.generate_ordered(ord)?;
5961            }
5962        }
5963        Ok(())
5964    }
5965
5966    fn generate_intersect(&mut self, intersect: &Intersect) -> Result<()> {
5967        // WITH clause
5968        if let Some(with) = &intersect.with {
5969            self.generate_with(with)?;
5970            self.write_space();
5971        }
5972        self.generate_expression(&intersect.left)?;
5973        if self.config.pretty {
5974            self.write_newline();
5975            self.write_indent();
5976        } else {
5977            self.write_space();
5978        }
5979
5980        // BigQuery set operation modifiers: [side] [kind] INTERSECT
5981        if let Some(side) = &intersect.side {
5982            self.write_keyword(side);
5983            self.write_space();
5984        }
5985        if let Some(kind) = &intersect.kind {
5986            self.write_keyword(kind);
5987            self.write_space();
5988        }
5989
5990        self.write_keyword("INTERSECT");
5991        if intersect.all {
5992            self.write_space();
5993            self.write_keyword("ALL");
5994        } else if intersect.distinct {
5995            self.write_space();
5996            self.write_keyword("DISTINCT");
5997        }
5998
5999        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6000        // DuckDB: BY NAME
6001        if intersect.corresponding || intersect.by_name {
6002            self.write_space();
6003            self.write_keyword("BY NAME");
6004        }
6005        if !intersect.on_columns.is_empty() {
6006            self.write_space();
6007            self.write_keyword("ON");
6008            self.write(" (");
6009            for (i, col) in intersect.on_columns.iter().enumerate() {
6010                if i > 0 {
6011                    self.write(", ");
6012                }
6013                self.generate_expression(col)?;
6014            }
6015            self.write(")");
6016        }
6017
6018        if self.config.pretty {
6019            self.write_newline();
6020            self.write_indent();
6021        } else {
6022            self.write_space();
6023        }
6024        self.generate_expression(&intersect.right)?;
6025        // ORDER BY, LIMIT, OFFSET for the set operation
6026        if let Some(order_by) = &intersect.order_by {
6027            if self.config.pretty {
6028                self.write_newline();
6029            } else {
6030                self.write_space();
6031            }
6032            self.write_keyword("ORDER BY");
6033            self.write_space();
6034            for (i, ordered) in order_by.expressions.iter().enumerate() {
6035                if i > 0 {
6036                    self.write(", ");
6037                }
6038                self.generate_ordered(ordered)?;
6039            }
6040        }
6041        if let Some(limit) = &intersect.limit {
6042            if self.config.pretty {
6043                self.write_newline();
6044            } else {
6045                self.write_space();
6046            }
6047            self.write_keyword("LIMIT");
6048            self.write_space();
6049            self.generate_expression(limit)?;
6050        }
6051        if let Some(offset) = &intersect.offset {
6052            if self.config.pretty {
6053                self.write_newline();
6054            } else {
6055                self.write_space();
6056            }
6057            self.write_keyword("OFFSET");
6058            self.write_space();
6059            self.generate_expression(offset)?;
6060        }
6061        // DISTRIBUTE BY (Hive/Spark)
6062        if let Some(distribute_by) = &intersect.distribute_by {
6063            self.write_space();
6064            self.write_keyword("DISTRIBUTE BY");
6065            self.write_space();
6066            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6067                if i > 0 {
6068                    self.write(", ");
6069                }
6070                self.generate_expression(expr)?;
6071            }
6072        }
6073        // SORT BY (Hive/Spark)
6074        if let Some(sort_by) = &intersect.sort_by {
6075            self.write_space();
6076            self.write_keyword("SORT BY");
6077            self.write_space();
6078            for (i, ord) in sort_by.expressions.iter().enumerate() {
6079                if i > 0 {
6080                    self.write(", ");
6081                }
6082                self.generate_ordered(ord)?;
6083            }
6084        }
6085        // CLUSTER BY (Hive/Spark)
6086        if let Some(cluster_by) = &intersect.cluster_by {
6087            self.write_space();
6088            self.write_keyword("CLUSTER BY");
6089            self.write_space();
6090            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6091                if i > 0 {
6092                    self.write(", ");
6093                }
6094                self.generate_ordered(ord)?;
6095            }
6096        }
6097        Ok(())
6098    }
6099
6100    fn generate_except(&mut self, except: &Except) -> Result<()> {
6101        use crate::dialects::DialectType;
6102
6103        // WITH clause
6104        if let Some(with) = &except.with {
6105            self.generate_with(with)?;
6106            self.write_space();
6107        }
6108
6109        self.generate_expression(&except.left)?;
6110        if self.config.pretty {
6111            self.write_newline();
6112            self.write_indent();
6113        } else {
6114            self.write_space();
6115        }
6116
6117        // BigQuery set operation modifiers: [side] [kind] EXCEPT
6118        if let Some(side) = &except.side {
6119            self.write_keyword(side);
6120            self.write_space();
6121        }
6122        if let Some(kind) = &except.kind {
6123            self.write_keyword(kind);
6124            self.write_space();
6125        }
6126
6127        // Oracle uses MINUS instead of EXCEPT (but not for EXCEPT ALL)
6128        match self.config.dialect {
6129            Some(DialectType::Oracle) if !except.all => {
6130                self.write_keyword("MINUS");
6131            }
6132            Some(DialectType::ClickHouse) => {
6133                // ClickHouse: drop ALL from EXCEPT ALL
6134                self.write_keyword("EXCEPT");
6135                if except.distinct {
6136                    self.write_space();
6137                    self.write_keyword("DISTINCT");
6138                }
6139            }
6140            Some(DialectType::BigQuery) => {
6141                // BigQuery: bare EXCEPT defaults to EXCEPT DISTINCT
6142                self.write_keyword("EXCEPT");
6143                if except.all {
6144                    self.write_space();
6145                    self.write_keyword("ALL");
6146                } else {
6147                    self.write_space();
6148                    self.write_keyword("DISTINCT");
6149                }
6150            }
6151            _ => {
6152                self.write_keyword("EXCEPT");
6153                if except.all {
6154                    self.write_space();
6155                    self.write_keyword("ALL");
6156                } else if except.distinct {
6157                    self.write_space();
6158                    self.write_keyword("DISTINCT");
6159                }
6160            }
6161        }
6162
6163        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6164        // DuckDB: BY NAME
6165        if except.corresponding || except.by_name {
6166            self.write_space();
6167            self.write_keyword("BY NAME");
6168        }
6169        if !except.on_columns.is_empty() {
6170            self.write_space();
6171            self.write_keyword("ON");
6172            self.write(" (");
6173            for (i, col) in except.on_columns.iter().enumerate() {
6174                if i > 0 {
6175                    self.write(", ");
6176                }
6177                self.generate_expression(col)?;
6178            }
6179            self.write(")");
6180        }
6181
6182        if self.config.pretty {
6183            self.write_newline();
6184            self.write_indent();
6185        } else {
6186            self.write_space();
6187        }
6188        self.generate_expression(&except.right)?;
6189        // ORDER BY, LIMIT, OFFSET for the set operation
6190        if let Some(order_by) = &except.order_by {
6191            if self.config.pretty {
6192                self.write_newline();
6193            } else {
6194                self.write_space();
6195            }
6196            self.write_keyword("ORDER BY");
6197            self.write_space();
6198            for (i, ordered) in order_by.expressions.iter().enumerate() {
6199                if i > 0 {
6200                    self.write(", ");
6201                }
6202                self.generate_ordered(ordered)?;
6203            }
6204        }
6205        if let Some(limit) = &except.limit {
6206            if self.config.pretty {
6207                self.write_newline();
6208            } else {
6209                self.write_space();
6210            }
6211            self.write_keyword("LIMIT");
6212            self.write_space();
6213            self.generate_expression(limit)?;
6214        }
6215        if let Some(offset) = &except.offset {
6216            if self.config.pretty {
6217                self.write_newline();
6218            } else {
6219                self.write_space();
6220            }
6221            self.write_keyword("OFFSET");
6222            self.write_space();
6223            self.generate_expression(offset)?;
6224        }
6225        // DISTRIBUTE BY (Hive/Spark)
6226        if let Some(distribute_by) = &except.distribute_by {
6227            self.write_space();
6228            self.write_keyword("DISTRIBUTE BY");
6229            self.write_space();
6230            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6231                if i > 0 {
6232                    self.write(", ");
6233                }
6234                self.generate_expression(expr)?;
6235            }
6236        }
6237        // SORT BY (Hive/Spark)
6238        if let Some(sort_by) = &except.sort_by {
6239            self.write_space();
6240            self.write_keyword("SORT BY");
6241            self.write_space();
6242            for (i, ord) in sort_by.expressions.iter().enumerate() {
6243                if i > 0 {
6244                    self.write(", ");
6245                }
6246                self.generate_ordered(ord)?;
6247            }
6248        }
6249        // CLUSTER BY (Hive/Spark)
6250        if let Some(cluster_by) = &except.cluster_by {
6251            self.write_space();
6252            self.write_keyword("CLUSTER BY");
6253            self.write_space();
6254            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6255                if i > 0 {
6256                    self.write(", ");
6257                }
6258                self.generate_ordered(ord)?;
6259            }
6260        }
6261        Ok(())
6262    }
6263
6264    fn generate_insert(&mut self, insert: &Insert) -> Result<()> {
6265        // For TSQL/Fabric/Spark/Hive/Databricks, CTEs must be prepended before INSERT
6266        let prepend_query_cte = if insert.with.is_none() {
6267            use crate::dialects::DialectType;
6268            let should_prepend = matches!(
6269                self.config.dialect,
6270                Some(DialectType::TSQL)
6271                    | Some(DialectType::Fabric)
6272                    | Some(DialectType::Spark)
6273                    | Some(DialectType::Databricks)
6274                    | Some(DialectType::Hive)
6275            );
6276            if should_prepend {
6277                if let Some(Expression::Select(select)) = &insert.query {
6278                    select.with.clone()
6279                } else {
6280                    None
6281                }
6282            } else {
6283                None
6284            }
6285        } else {
6286            None
6287        };
6288
6289        // Output WITH clause if on INSERT (e.g., WITH ... INSERT INTO ...)
6290        if let Some(with) = &insert.with {
6291            self.generate_with(with)?;
6292            self.write_space();
6293        } else if let Some(with) = &prepend_query_cte {
6294            self.generate_with(with)?;
6295            self.write_space();
6296        }
6297
6298        // Output leading comments before INSERT
6299        for comment in &insert.leading_comments {
6300            self.write_formatted_comment(comment);
6301            self.write(" ");
6302        }
6303
6304        // Handle directory insert (INSERT OVERWRITE DIRECTORY)
6305        if let Some(dir) = &insert.directory {
6306            self.write_keyword("INSERT OVERWRITE");
6307            if dir.local {
6308                self.write_space();
6309                self.write_keyword("LOCAL");
6310            }
6311            self.write_space();
6312            self.write_keyword("DIRECTORY");
6313            self.write_space();
6314            self.write("'");
6315            self.write(&dir.path);
6316            self.write("'");
6317
6318            // ROW FORMAT clause
6319            if let Some(row_format) = &dir.row_format {
6320                self.write_space();
6321                self.write_keyword("ROW FORMAT");
6322                if row_format.delimited {
6323                    self.write_space();
6324                    self.write_keyword("DELIMITED");
6325                }
6326                if let Some(val) = &row_format.fields_terminated_by {
6327                    self.write_space();
6328                    self.write_keyword("FIELDS TERMINATED BY");
6329                    self.write_space();
6330                    self.write("'");
6331                    self.write(val);
6332                    self.write("'");
6333                }
6334                if let Some(val) = &row_format.collection_items_terminated_by {
6335                    self.write_space();
6336                    self.write_keyword("COLLECTION ITEMS TERMINATED BY");
6337                    self.write_space();
6338                    self.write("'");
6339                    self.write(val);
6340                    self.write("'");
6341                }
6342                if let Some(val) = &row_format.map_keys_terminated_by {
6343                    self.write_space();
6344                    self.write_keyword("MAP KEYS TERMINATED BY");
6345                    self.write_space();
6346                    self.write("'");
6347                    self.write(val);
6348                    self.write("'");
6349                }
6350                if let Some(val) = &row_format.lines_terminated_by {
6351                    self.write_space();
6352                    self.write_keyword("LINES TERMINATED BY");
6353                    self.write_space();
6354                    self.write("'");
6355                    self.write(val);
6356                    self.write("'");
6357                }
6358                if let Some(val) = &row_format.null_defined_as {
6359                    self.write_space();
6360                    self.write_keyword("NULL DEFINED AS");
6361                    self.write_space();
6362                    self.write("'");
6363                    self.write(val);
6364                    self.write("'");
6365                }
6366            }
6367
6368            // STORED AS clause
6369            if let Some(format) = &dir.stored_as {
6370                self.write_space();
6371                self.write_keyword("STORED AS");
6372                self.write_space();
6373                self.write_keyword(format);
6374            }
6375
6376            // Query (SELECT statement)
6377            if let Some(query) = &insert.query {
6378                self.write_space();
6379                self.generate_expression(query)?;
6380            }
6381
6382            return Ok(());
6383        }
6384
6385        if insert.is_replace {
6386            // MySQL/SQLite REPLACE INTO statement
6387            self.write_keyword("REPLACE INTO");
6388        } else if insert.overwrite {
6389            // Use dialect-specific INSERT OVERWRITE format
6390            self.write_keyword("INSERT");
6391            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
6392            if let Some(ref hint) = insert.hint {
6393                self.generate_hint(hint)?;
6394            }
6395            self.write(&self.config.insert_overwrite.to_uppercase());
6396        } else if let Some(ref action) = insert.conflict_action {
6397            // SQLite conflict action: INSERT OR ABORT|FAIL|IGNORE|REPLACE|ROLLBACK INTO
6398            self.write_keyword("INSERT OR");
6399            self.write_space();
6400            self.write_keyword(action);
6401            self.write_space();
6402            self.write_keyword("INTO");
6403        } else if insert.ignore {
6404            // MySQL INSERT IGNORE syntax
6405            self.write_keyword("INSERT IGNORE INTO");
6406        } else {
6407            self.write_keyword("INSERT");
6408            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
6409            if let Some(ref hint) = insert.hint {
6410                self.generate_hint(hint)?;
6411            }
6412            self.write_space();
6413            self.write_keyword("INTO");
6414        }
6415        // ClickHouse: INSERT INTO FUNCTION func_name(args...)
6416        if let Some(ref func) = insert.function_target {
6417            self.write_space();
6418            self.write_keyword("FUNCTION");
6419            self.write_space();
6420            self.generate_expression(func)?;
6421        } else {
6422            self.write_space();
6423            self.generate_table(&insert.table)?;
6424        }
6425
6426        // Table alias (PostgreSQL: INSERT INTO table AS t(...), Oracle: INSERT INTO table t ...)
6427        if let Some(ref alias) = insert.alias {
6428            self.write_space();
6429            if insert.alias_explicit_as {
6430                self.write_keyword("AS");
6431                self.write_space();
6432            }
6433            self.generate_identifier(alias)?;
6434        }
6435
6436        // IF EXISTS clause (Hive)
6437        if insert.if_exists {
6438            self.write_space();
6439            self.write_keyword("IF EXISTS");
6440        }
6441
6442        // REPLACE WHERE clause (Databricks)
6443        if let Some(ref replace_where) = insert.replace_where {
6444            if self.config.pretty {
6445                self.write_newline();
6446                self.write_indent();
6447            } else {
6448                self.write_space();
6449            }
6450            self.write_keyword("REPLACE WHERE");
6451            self.write_space();
6452            self.generate_expression(replace_where)?;
6453        }
6454
6455        // Generate PARTITION clause if present
6456        if !insert.partition.is_empty() {
6457            self.write_space();
6458            self.write_keyword("PARTITION");
6459            self.write("(");
6460            for (i, (col, val)) in insert.partition.iter().enumerate() {
6461                if i > 0 {
6462                    self.write(", ");
6463                }
6464                self.generate_identifier(col)?;
6465                if let Some(v) = val {
6466                    self.write(" = ");
6467                    self.generate_expression(v)?;
6468                }
6469            }
6470            self.write(")");
6471        }
6472
6473        // ClickHouse: PARTITION BY expr
6474        if let Some(ref partition_by) = insert.partition_by {
6475            self.write_space();
6476            self.write_keyword("PARTITION BY");
6477            self.write_space();
6478            self.generate_expression(partition_by)?;
6479        }
6480
6481        // ClickHouse: SETTINGS key = val, ...
6482        if !insert.settings.is_empty() {
6483            self.write_space();
6484            self.write_keyword("SETTINGS");
6485            self.write_space();
6486            for (i, setting) in insert.settings.iter().enumerate() {
6487                if i > 0 {
6488                    self.write(", ");
6489                }
6490                self.generate_expression(setting)?;
6491            }
6492        }
6493
6494        if !insert.columns.is_empty() {
6495            if insert.alias.is_some() && insert.alias_explicit_as {
6496                // No space when explicit AS alias is present: INSERT INTO table AS t(a, b, c)
6497                self.write("(");
6498            } else {
6499                // Space for implicit alias or no alias: INSERT INTO dest d (i, value)
6500                self.write(" (");
6501            }
6502            for (i, col) in insert.columns.iter().enumerate() {
6503                if i > 0 {
6504                    self.write(", ");
6505                }
6506                self.generate_identifier(col)?;
6507            }
6508            self.write(")");
6509        }
6510
6511        // OUTPUT clause (TSQL)
6512        if let Some(ref output) = insert.output {
6513            self.generate_output_clause(output)?;
6514        }
6515
6516        // BY NAME modifier (DuckDB)
6517        if insert.by_name {
6518            self.write_space();
6519            self.write_keyword("BY NAME");
6520        }
6521
6522        if insert.default_values {
6523            self.write_space();
6524            self.write_keyword("DEFAULT VALUES");
6525        } else if let Some(query) = &insert.query {
6526            if self.config.pretty {
6527                self.write_newline();
6528            } else {
6529                self.write_space();
6530            }
6531            // If we prepended CTEs from nested SELECT (TSQL), strip the WITH from SELECT
6532            if prepend_query_cte.is_some() {
6533                if let Expression::Select(select) = query {
6534                    let mut select_no_with = select.clone();
6535                    select_no_with.with = None;
6536                    self.generate_select(&select_no_with)?;
6537                } else {
6538                    self.generate_expression(query)?;
6539                }
6540            } else {
6541                self.generate_expression(query)?;
6542            }
6543        } else if !insert.values.is_empty() {
6544            if self.config.pretty {
6545                // Pretty printing: VALUES on new line, each tuple indented
6546                self.write_newline();
6547                self.write_keyword("VALUES");
6548                self.write_newline();
6549                self.indent_level += 1;
6550                for (i, row) in insert.values.iter().enumerate() {
6551                    if i > 0 {
6552                        self.write(",");
6553                        self.write_newline();
6554                    }
6555                    self.write_indent();
6556                    self.write("(");
6557                    for (j, val) in row.iter().enumerate() {
6558                        if j > 0 {
6559                            self.write(", ");
6560                        }
6561                        self.generate_expression(val)?;
6562                    }
6563                    self.write(")");
6564                }
6565                self.indent_level -= 1;
6566            } else {
6567                // Non-pretty: single line
6568                self.write_space();
6569                self.write_keyword("VALUES");
6570                for (i, row) in insert.values.iter().enumerate() {
6571                    if i > 0 {
6572                        self.write(",");
6573                    }
6574                    self.write(" (");
6575                    for (j, val) in row.iter().enumerate() {
6576                        if j > 0 {
6577                            self.write(", ");
6578                        }
6579                        self.generate_expression(val)?;
6580                    }
6581                    self.write(")");
6582                }
6583            }
6584        }
6585
6586        // Source table (Hive/Spark): INSERT OVERWRITE TABLE target TABLE source
6587        if let Some(ref source) = insert.source {
6588            self.write_space();
6589            self.write_keyword("TABLE");
6590            self.write_space();
6591            self.generate_expression(source)?;
6592        }
6593
6594        // Source alias (MySQL: VALUES (...) AS new_data)
6595        if let Some(alias) = &insert.source_alias {
6596            self.write_space();
6597            self.write_keyword("AS");
6598            self.write_space();
6599            self.generate_identifier(alias)?;
6600        }
6601
6602        // ON CONFLICT clause (Materialize doesn't support ON CONFLICT)
6603        if let Some(on_conflict) = &insert.on_conflict {
6604            if !matches!(self.config.dialect, Some(DialectType::Materialize)) {
6605                self.write_space();
6606                self.generate_expression(on_conflict)?;
6607            }
6608        }
6609
6610        // RETURNING clause
6611        if !insert.returning.is_empty() {
6612            self.write_space();
6613            self.write_keyword("RETURNING");
6614            self.write_space();
6615            for (i, expr) in insert.returning.iter().enumerate() {
6616                if i > 0 {
6617                    self.write(", ");
6618                }
6619                self.generate_expression(expr)?;
6620            }
6621        }
6622
6623        Ok(())
6624    }
6625
6626    fn generate_update(&mut self, update: &Update) -> Result<()> {
6627        // Output leading comments before UPDATE
6628        for comment in &update.leading_comments {
6629            self.write_formatted_comment(comment);
6630            self.write(" ");
6631        }
6632
6633        // WITH clause (CTEs)
6634        if let Some(ref with) = update.with {
6635            self.generate_with(with)?;
6636            self.write_space();
6637        }
6638
6639        self.write_keyword("UPDATE");
6640        self.write_space();
6641        self.generate_table(&update.table)?;
6642
6643        let mysql_like_update_from = matches!(
6644            self.config.dialect,
6645            Some(DialectType::MySQL) | Some(DialectType::SingleStore)
6646        ) && update.from_clause.is_some();
6647
6648        let mut set_pairs = update.set.clone();
6649
6650        // MySQL-style UPDATE doesn't support FROM after SET. Convert FROM tables to JOIN ... ON TRUE.
6651        let mut pre_set_joins = update.table_joins.clone();
6652        if mysql_like_update_from {
6653            let target_name = update
6654                .table
6655                .alias
6656                .as_ref()
6657                .map(|a| a.name.clone())
6658                .unwrap_or_else(|| update.table.name.name.clone());
6659
6660            for (col, _) in &mut set_pairs {
6661                if !col.name.contains('.') {
6662                    col.name = format!("{}.{}", target_name, col.name);
6663                }
6664            }
6665
6666            if let Some(from_clause) = &update.from_clause {
6667                for table_expr in &from_clause.expressions {
6668                    pre_set_joins.push(crate::expressions::Join {
6669                        this: table_expr.clone(),
6670                        on: Some(Expression::Boolean(crate::expressions::BooleanLiteral {
6671                            value: true,
6672                        })),
6673                        using: Vec::new(),
6674                        kind: crate::expressions::JoinKind::Inner,
6675                        use_inner_keyword: false,
6676                        use_outer_keyword: false,
6677                        deferred_condition: false,
6678                        join_hint: None,
6679                        match_condition: None,
6680                        pivots: Vec::new(),
6681                        comments: Vec::new(),
6682                        nesting_group: 0,
6683                        directed: false,
6684                    });
6685                }
6686            }
6687            for join in &update.from_joins {
6688                let mut join = join.clone();
6689                if join.on.is_none() && join.using.is_empty() {
6690                    join.on = Some(Expression::Boolean(crate::expressions::BooleanLiteral {
6691                        value: true,
6692                    }));
6693                }
6694                pre_set_joins.push(join);
6695            }
6696        }
6697
6698        // Extra tables for multi-table UPDATE (MySQL syntax)
6699        for extra_table in &update.extra_tables {
6700            self.write(", ");
6701            self.generate_table(extra_table)?;
6702        }
6703
6704        // JOINs attached to the table list (MySQL multi-table syntax)
6705        for join in &pre_set_joins {
6706            // generate_join already adds a leading space
6707            self.generate_join(join)?;
6708        }
6709
6710        // Teradata: FROM clause comes before SET
6711        let teradata_from_before_set = matches!(self.config.dialect, Some(DialectType::Teradata));
6712        if teradata_from_before_set && !mysql_like_update_from {
6713            if let Some(ref from_clause) = update.from_clause {
6714                self.write_space();
6715                self.write_keyword("FROM");
6716                self.write_space();
6717                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
6718                    if i > 0 {
6719                        self.write(", ");
6720                    }
6721                    self.generate_expression(table_expr)?;
6722                }
6723            }
6724            for join in &update.from_joins {
6725                self.generate_join(join)?;
6726            }
6727        }
6728
6729        self.write_space();
6730        self.write_keyword("SET");
6731        self.write_space();
6732
6733        for (i, (col, val)) in set_pairs.iter().enumerate() {
6734            if i > 0 {
6735                self.write(", ");
6736            }
6737            self.generate_identifier(col)?;
6738            self.write(" = ");
6739            self.generate_expression(val)?;
6740        }
6741
6742        // OUTPUT clause (TSQL)
6743        if let Some(ref output) = update.output {
6744            self.generate_output_clause(output)?;
6745        }
6746
6747        // FROM clause (after SET for non-Teradata, non-MySQL dialects)
6748        if !mysql_like_update_from && !teradata_from_before_set {
6749            if let Some(ref from_clause) = update.from_clause {
6750                self.write_space();
6751                self.write_keyword("FROM");
6752                self.write_space();
6753                // Generate each table in the FROM clause
6754                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
6755                    if i > 0 {
6756                        self.write(", ");
6757                    }
6758                    self.generate_expression(table_expr)?;
6759                }
6760            }
6761        }
6762
6763        if !mysql_like_update_from && !teradata_from_before_set {
6764            // JOINs after FROM clause (PostgreSQL, Snowflake, SQL Server syntax)
6765            for join in &update.from_joins {
6766                self.generate_join(join)?;
6767            }
6768        }
6769
6770        if let Some(where_clause) = &update.where_clause {
6771            self.write_space();
6772            self.write_keyword("WHERE");
6773            self.write_space();
6774            self.generate_expression(&where_clause.this)?;
6775        }
6776
6777        // RETURNING clause
6778        if !update.returning.is_empty() {
6779            self.write_space();
6780            self.write_keyword("RETURNING");
6781            self.write_space();
6782            for (i, expr) in update.returning.iter().enumerate() {
6783                if i > 0 {
6784                    self.write(", ");
6785                }
6786                self.generate_expression(expr)?;
6787            }
6788        }
6789
6790        // ORDER BY clause (MySQL)
6791        if let Some(ref order_by) = update.order_by {
6792            self.write_space();
6793            self.generate_order_by(order_by)?;
6794        }
6795
6796        // LIMIT clause (MySQL)
6797        if let Some(ref limit) = update.limit {
6798            self.write_space();
6799            self.write_keyword("LIMIT");
6800            self.write_space();
6801            self.generate_expression(limit)?;
6802        }
6803
6804        Ok(())
6805    }
6806
6807    fn generate_delete(&mut self, delete: &Delete) -> Result<()> {
6808        // Output WITH clause if present
6809        if let Some(with) = &delete.with {
6810            self.generate_with(with)?;
6811            self.write_space();
6812        }
6813
6814        // Output leading comments before DELETE
6815        for comment in &delete.leading_comments {
6816            self.write_formatted_comment(comment);
6817            self.write(" ");
6818        }
6819
6820        // MySQL multi-table DELETE or TSQL DELETE with OUTPUT before FROM
6821        if !delete.tables.is_empty() && !delete.tables_from_using {
6822            // DELETE t1[, t2] [OUTPUT ...] FROM ... syntax (tables before FROM)
6823            self.write_keyword("DELETE");
6824            self.write_space();
6825            for (i, tbl) in delete.tables.iter().enumerate() {
6826                if i > 0 {
6827                    self.write(", ");
6828                }
6829                self.generate_table(tbl)?;
6830            }
6831            // TSQL: OUTPUT clause between target table and FROM
6832            if let Some(ref output) = delete.output {
6833                self.generate_output_clause(output)?;
6834            }
6835            self.write_space();
6836            self.write_keyword("FROM");
6837            self.write_space();
6838            self.generate_table(&delete.table)?;
6839        } else if !delete.tables.is_empty() && delete.tables_from_using {
6840            // DELETE FROM t1, t2 USING ... syntax (tables after FROM)
6841            self.write_keyword("DELETE FROM");
6842            self.write_space();
6843            for (i, tbl) in delete.tables.iter().enumerate() {
6844                if i > 0 {
6845                    self.write(", ");
6846                }
6847                self.generate_table(tbl)?;
6848            }
6849        } else if delete.no_from && matches!(self.config.dialect, Some(DialectType::BigQuery)) {
6850            // BigQuery-style DELETE without FROM keyword
6851            self.write_keyword("DELETE");
6852            self.write_space();
6853            self.generate_table(&delete.table)?;
6854        } else {
6855            self.write_keyword("DELETE FROM");
6856            self.write_space();
6857            self.generate_table(&delete.table)?;
6858        }
6859
6860        // ClickHouse: ON CLUSTER clause
6861        if let Some(ref on_cluster) = delete.on_cluster {
6862            self.write_space();
6863            self.generate_on_cluster(on_cluster)?;
6864        }
6865
6866        // FORCE INDEX hint (MySQL)
6867        if let Some(ref idx) = delete.force_index {
6868            self.write_space();
6869            self.write_keyword("FORCE INDEX");
6870            self.write(" (");
6871            self.write(idx);
6872            self.write(")");
6873        }
6874
6875        // Optional alias
6876        if let Some(ref alias) = delete.alias {
6877            self.write_space();
6878            if delete.alias_explicit_as
6879                || matches!(self.config.dialect, Some(DialectType::BigQuery))
6880            {
6881                self.write_keyword("AS");
6882                self.write_space();
6883            }
6884            self.generate_identifier(alias)?;
6885        }
6886
6887        // JOINs (MySQL multi-table) - when NOT tables_from_using, JOINs come before USING
6888        if !delete.tables_from_using {
6889            for join in &delete.joins {
6890                self.generate_join(join)?;
6891            }
6892        }
6893
6894        // USING clause (PostgreSQL/DuckDB/MySQL)
6895        if !delete.using.is_empty() {
6896            self.write_space();
6897            self.write_keyword("USING");
6898            for (i, table) in delete.using.iter().enumerate() {
6899                if i > 0 {
6900                    self.write(",");
6901                }
6902                self.write_space();
6903                // Check if the table has subquery hints (DuckDB USING with subquery)
6904                if !table.hints.is_empty() && table.name.is_empty() {
6905                    // Subquery in USING: (VALUES ...) AS alias(cols)
6906                    self.generate_expression(&table.hints[0])?;
6907                    if let Some(ref alias) = table.alias {
6908                        self.write_space();
6909                        if table.alias_explicit_as {
6910                            self.write_keyword("AS");
6911                            self.write_space();
6912                        }
6913                        self.generate_identifier(alias)?;
6914                        if !table.column_aliases.is_empty() {
6915                            self.write("(");
6916                            for (j, col_alias) in table.column_aliases.iter().enumerate() {
6917                                if j > 0 {
6918                                    self.write(", ");
6919                                }
6920                                self.generate_identifier(col_alias)?;
6921                            }
6922                            self.write(")");
6923                        }
6924                    }
6925                } else {
6926                    self.generate_table(table)?;
6927                }
6928            }
6929        }
6930
6931        // JOINs (MySQL multi-table) - when tables_from_using, JOINs come after USING
6932        if delete.tables_from_using {
6933            for join in &delete.joins {
6934                self.generate_join(join)?;
6935            }
6936        }
6937
6938        // OUTPUT clause (TSQL) - only if not already emitted in the early position
6939        let output_already_emitted =
6940            !delete.tables.is_empty() && !delete.tables_from_using && delete.output.is_some();
6941        if !output_already_emitted {
6942            if let Some(ref output) = delete.output {
6943                self.generate_output_clause(output)?;
6944            }
6945        }
6946
6947        if let Some(where_clause) = &delete.where_clause {
6948            self.write_space();
6949            self.write_keyword("WHERE");
6950            self.write_space();
6951            self.generate_expression(&where_clause.this)?;
6952        }
6953
6954        // ORDER BY clause (MySQL)
6955        if let Some(ref order_by) = delete.order_by {
6956            self.write_space();
6957            self.generate_order_by(order_by)?;
6958        }
6959
6960        // LIMIT clause (MySQL)
6961        if let Some(ref limit) = delete.limit {
6962            self.write_space();
6963            self.write_keyword("LIMIT");
6964            self.write_space();
6965            self.generate_expression(limit)?;
6966        }
6967
6968        // RETURNING clause (PostgreSQL)
6969        if !delete.returning.is_empty() {
6970            self.write_space();
6971            self.write_keyword("RETURNING");
6972            self.write_space();
6973            for (i, expr) in delete.returning.iter().enumerate() {
6974                if i > 0 {
6975                    self.write(", ");
6976                }
6977                self.generate_expression(expr)?;
6978            }
6979        }
6980
6981        Ok(())
6982    }
6983
6984    // ==================== DDL Generation ====================
6985
6986    fn generate_create_table(&mut self, ct: &CreateTable) -> Result<()> {
6987        // Athena: Determine if this is Hive-style DDL or Trino-style DML
6988        // CREATE TABLE AS SELECT uses Trino (double quotes)
6989        // CREATE TABLE (without AS SELECT) and CREATE EXTERNAL TABLE use Hive (backticks)
6990        let saved_athena_hive_context = self.athena_hive_context;
6991        let is_clickhouse = matches!(self.config.dialect, Some(DialectType::ClickHouse));
6992        if matches!(
6993            self.config.dialect,
6994            Some(crate::dialects::DialectType::Athena)
6995        ) {
6996            // Use Hive context if:
6997            // 1. It's an EXTERNAL table, OR
6998            // 2. There's no AS SELECT clause
6999            let is_external = ct
7000                .table_modifier
7001                .as_ref()
7002                .map(|m| m.eq_ignore_ascii_case("EXTERNAL"))
7003                .unwrap_or(false);
7004            let has_as_select = ct.as_select.is_some();
7005            self.athena_hive_context = is_external || !has_as_select;
7006        }
7007
7008        // TSQL: Convert CREATE TABLE AS SELECT to SELECT * INTO table FROM (subquery) AS temp
7009        if matches!(
7010            self.config.dialect,
7011            Some(crate::dialects::DialectType::TSQL)
7012        ) {
7013            if let Some(ref query) = ct.as_select {
7014                // Output WITH CTE clause if present
7015                if let Some(with_cte) = &ct.with_cte {
7016                    self.generate_with(with_cte)?;
7017                    self.write_space();
7018                }
7019
7020                // Generate: SELECT * INTO [table] FROM (subquery) AS temp
7021                self.write_keyword("SELECT");
7022                self.write(" * ");
7023                self.write_keyword("INTO");
7024                self.write_space();
7025
7026                // If temporary, prefix with # for TSQL temp table
7027                if ct.temporary {
7028                    self.write("#");
7029                }
7030                self.generate_table(&ct.name)?;
7031
7032                self.write_space();
7033                self.write_keyword("FROM");
7034                self.write(" (");
7035                // For TSQL, add aliases to select columns to preserve column names
7036                let aliased_query = Self::add_column_aliases_to_query(query.clone());
7037                self.generate_expression(&aliased_query)?;
7038                self.write(") ");
7039                self.write_keyword("AS");
7040                self.write(" temp");
7041                return Ok(());
7042            }
7043        }
7044
7045        // Output WITH CTE clause if present
7046        if let Some(with_cte) = &ct.with_cte {
7047            self.generate_with(with_cte)?;
7048            self.write_space();
7049        }
7050
7051        // Output leading comments before CREATE
7052        for comment in &ct.leading_comments {
7053            self.write_formatted_comment(comment);
7054            self.write(" ");
7055        }
7056        self.write_keyword("CREATE");
7057
7058        if ct.or_replace {
7059            self.write_space();
7060            self.write_keyword("OR REPLACE");
7061        }
7062
7063        if ct.temporary {
7064            self.write_space();
7065            // Oracle uses GLOBAL TEMPORARY TABLE syntax
7066            if matches!(self.config.dialect, Some(DialectType::Oracle)) {
7067                self.write_keyword("GLOBAL TEMPORARY");
7068            } else {
7069                self.write_keyword("TEMPORARY");
7070            }
7071        }
7072
7073        // Table modifier: DYNAMIC, ICEBERG, EXTERNAL, HYBRID, TRANSIENT
7074        let is_dictionary = ct
7075            .table_modifier
7076            .as_ref()
7077            .map(|m| m.eq_ignore_ascii_case("DICTIONARY"))
7078            .unwrap_or(false);
7079        if let Some(ref modifier) = ct.table_modifier {
7080            // TRANSIENT is Snowflake-specific - skip for other dialects
7081            let skip_transient = modifier.eq_ignore_ascii_case("TRANSIENT")
7082                && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None);
7083            // Teradata-specific modifiers: VOLATILE, SET, MULTISET, SET TABLE combinations
7084            let is_teradata_modifier = modifier.eq_ignore_ascii_case("VOLATILE")
7085                || modifier.eq_ignore_ascii_case("SET")
7086                || modifier.eq_ignore_ascii_case("MULTISET")
7087                || modifier.to_uppercase().contains("VOLATILE")
7088                || modifier.to_uppercase().starts_with("SET ")
7089                || modifier.to_uppercase().starts_with("MULTISET ");
7090            let skip_teradata =
7091                is_teradata_modifier && !matches!(self.config.dialect, Some(DialectType::Teradata));
7092            if !skip_transient && !skip_teradata {
7093                self.write_space();
7094                self.write_keyword(modifier);
7095            }
7096        }
7097
7098        if !is_dictionary {
7099            self.write_space();
7100            self.write_keyword("TABLE");
7101        }
7102
7103        if ct.if_not_exists {
7104            self.write_space();
7105            self.write_keyword("IF NOT EXISTS");
7106        }
7107
7108        self.write_space();
7109        self.generate_table(&ct.name)?;
7110
7111        // ClickHouse: ON CLUSTER clause
7112        if let Some(ref on_cluster) = ct.on_cluster {
7113            self.write_space();
7114            self.generate_on_cluster(on_cluster)?;
7115        }
7116
7117        // Teradata: options after table name before column list (comma-separated)
7118        if matches!(
7119            self.config.dialect,
7120            Some(crate::dialects::DialectType::Teradata)
7121        ) && !ct.teradata_post_name_options.is_empty()
7122        {
7123            for opt in &ct.teradata_post_name_options {
7124                self.write(", ");
7125                self.write(opt);
7126            }
7127        }
7128
7129        // Snowflake: COPY GRANTS clause
7130        if ct.copy_grants {
7131            self.write_space();
7132            self.write_keyword("COPY GRANTS");
7133        }
7134
7135        // Snowflake: USING TEMPLATE clause (before columns or AS SELECT)
7136        if let Some(ref using_template) = ct.using_template {
7137            self.write_space();
7138            self.write_keyword("USING TEMPLATE");
7139            self.write_space();
7140            self.generate_expression(using_template)?;
7141            return Ok(());
7142        }
7143
7144        // Handle [SHALLOW | DEEP] CLONE/COPY source_table [AT(...) | BEFORE(...)]
7145        if let Some(ref clone_source) = ct.clone_source {
7146            self.write_space();
7147            if ct.is_copy && self.config.supports_table_copy {
7148                // BigQuery uses COPY
7149                self.write_keyword("COPY");
7150            } else if ct.shallow_clone {
7151                self.write_keyword("SHALLOW CLONE");
7152            } else {
7153                self.write_keyword("CLONE");
7154            }
7155            self.write_space();
7156            self.generate_table(clone_source)?;
7157            // Generate AT/BEFORE time travel clause (stored as Raw expression)
7158            if let Some(ref at_clause) = ct.clone_at_clause {
7159                self.write_space();
7160                self.generate_expression(at_clause)?;
7161            }
7162            return Ok(());
7163        }
7164
7165        // Handle PARTITION OF property
7166        // Output order: PARTITION OF <table> (<columns/constraints>) FOR VALUES ...
7167        // Columns/constraints must appear BETWEEN the table name and the partition bound spec
7168        if let Some(ref partition_of) = ct.partition_of {
7169            self.write_space();
7170
7171            // Extract the PartitionedOfProperty parts to generate them separately
7172            if let Expression::PartitionedOfProperty(ref pop) = partition_of {
7173                // Output: PARTITION OF <table>
7174                self.write_keyword("PARTITION OF");
7175                self.write_space();
7176                self.generate_expression(&pop.this)?;
7177
7178                // Output columns/constraints if present (e.g., (unitsales DEFAULT 0) or (CONSTRAINT ...))
7179                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
7180                    self.write(" (");
7181                    let mut first = true;
7182                    for col in &ct.columns {
7183                        if !first {
7184                            self.write(", ");
7185                        }
7186                        first = false;
7187                        self.generate_column_def(col)?;
7188                    }
7189                    for constraint in &ct.constraints {
7190                        if !first {
7191                            self.write(", ");
7192                        }
7193                        first = false;
7194                        self.generate_table_constraint(constraint)?;
7195                    }
7196                    self.write(")");
7197                }
7198
7199                // Output partition bound spec: FOR VALUES ... or DEFAULT
7200                if let Expression::PartitionBoundSpec(_) = pop.expression.as_ref() {
7201                    self.write_space();
7202                    self.write_keyword("FOR VALUES");
7203                    self.write_space();
7204                    self.generate_expression(&pop.expression)?;
7205                } else {
7206                    self.write_space();
7207                    self.write_keyword("DEFAULT");
7208                }
7209            } else {
7210                // Fallback: generate the whole expression if it's not a PartitionedOfProperty
7211                self.generate_expression(partition_of)?;
7212
7213                // Output columns/constraints if present
7214                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
7215                    self.write(" (");
7216                    let mut first = true;
7217                    for col in &ct.columns {
7218                        if !first {
7219                            self.write(", ");
7220                        }
7221                        first = false;
7222                        self.generate_column_def(col)?;
7223                    }
7224                    for constraint in &ct.constraints {
7225                        if !first {
7226                            self.write(", ");
7227                        }
7228                        first = false;
7229                        self.generate_table_constraint(constraint)?;
7230                    }
7231                    self.write(")");
7232                }
7233            }
7234
7235            // Output table properties (e.g., PARTITION BY RANGE(population))
7236            for prop in &ct.properties {
7237                self.write_space();
7238                self.generate_expression(prop)?;
7239            }
7240
7241            return Ok(());
7242        }
7243
7244        // SQLite: Inline single-column PRIMARY KEY constraints into column definition
7245        // This matches Python sqlglot's behavior for SQLite dialect
7246        self.sqlite_inline_pk_columns.clear();
7247        if matches!(
7248            self.config.dialect,
7249            Some(crate::dialects::DialectType::SQLite)
7250        ) {
7251            for constraint in &ct.constraints {
7252                if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7253                    // Only inline if: single column, no constraint name, and column exists in table
7254                    if columns.len() == 1 && name.is_none() {
7255                        let pk_col_name = columns[0].name.to_lowercase();
7256                        // Check if this column exists in the table
7257                        if ct
7258                            .columns
7259                            .iter()
7260                            .any(|c| c.name.name.to_lowercase() == pk_col_name)
7261                        {
7262                            self.sqlite_inline_pk_columns.insert(pk_col_name);
7263                        }
7264                    }
7265                }
7266            }
7267        }
7268
7269        // Output columns if present (even for CTAS with columns)
7270        if !ct.columns.is_empty() {
7271            if self.config.pretty {
7272                // Pretty print: each column on new line
7273                self.write(" (");
7274                self.write_newline();
7275                self.indent_level += 1;
7276                for (i, col) in ct.columns.iter().enumerate() {
7277                    if i > 0 {
7278                        self.write(",");
7279                        self.write_newline();
7280                    }
7281                    self.write_indent();
7282                    self.generate_column_def(col)?;
7283                }
7284                // Table constraints (skip inlined PRIMARY KEY for SQLite)
7285                for constraint in &ct.constraints {
7286                    // Skip single-column PRIMARY KEY that was inlined for SQLite
7287                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7288                        if columns.len() == 1
7289                            && name.is_none()
7290                            && self
7291                                .sqlite_inline_pk_columns
7292                                .contains(&columns[0].name.to_lowercase())
7293                        {
7294                            continue;
7295                        }
7296                    }
7297                    self.write(",");
7298                    self.write_newline();
7299                    self.write_indent();
7300                    self.generate_table_constraint(constraint)?;
7301                }
7302                self.indent_level -= 1;
7303                self.write_newline();
7304                self.write(")");
7305            } else {
7306                self.write(" (");
7307                for (i, col) in ct.columns.iter().enumerate() {
7308                    if i > 0 {
7309                        self.write(", ");
7310                    }
7311                    self.generate_column_def(col)?;
7312                }
7313                // Table constraints (skip inlined PRIMARY KEY for SQLite)
7314                let mut first_constraint = true;
7315                for constraint in &ct.constraints {
7316                    // Skip single-column PRIMARY KEY that was inlined for SQLite
7317                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7318                        if columns.len() == 1
7319                            && name.is_none()
7320                            && self
7321                                .sqlite_inline_pk_columns
7322                                .contains(&columns[0].name.to_lowercase())
7323                        {
7324                            continue;
7325                        }
7326                    }
7327                    if first_constraint {
7328                        self.write(", ");
7329                        first_constraint = false;
7330                    } else {
7331                        self.write(", ");
7332                    }
7333                    self.generate_table_constraint(constraint)?;
7334                }
7335                self.write(")");
7336            }
7337        } else if !ct.constraints.is_empty() {
7338            // No columns but constraints exist (e.g., CREATE TABLE A LIKE B or CREATE TABLE A TAG (...))
7339            let has_like_only = ct
7340                .constraints
7341                .iter()
7342                .all(|c| matches!(c, TableConstraint::Like { .. }));
7343            let has_tags_only = ct
7344                .constraints
7345                .iter()
7346                .all(|c| matches!(c, TableConstraint::Tags(_)));
7347            // PostgreSQL: CREATE TABLE A (LIKE B INCLUDING ALL) (with parens)
7348            // Most dialects: CREATE TABLE A LIKE B (no parens)
7349            // Snowflake: CREATE TABLE A TAG (...) (no outer parens, but TAG has its own)
7350            let is_pg_like = matches!(
7351                self.config.dialect,
7352                Some(crate::dialects::DialectType::PostgreSQL)
7353                    | Some(crate::dialects::DialectType::CockroachDB)
7354                    | Some(crate::dialects::DialectType::Materialize)
7355                    | Some(crate::dialects::DialectType::RisingWave)
7356                    | Some(crate::dialects::DialectType::Redshift)
7357                    | Some(crate::dialects::DialectType::Presto)
7358                    | Some(crate::dialects::DialectType::Trino)
7359                    | Some(crate::dialects::DialectType::Athena)
7360            );
7361            let use_parens = if has_like_only {
7362                is_pg_like
7363            } else {
7364                !has_tags_only
7365            };
7366            if self.config.pretty && use_parens {
7367                self.write(" (");
7368                self.write_newline();
7369                self.indent_level += 1;
7370                for (i, constraint) in ct.constraints.iter().enumerate() {
7371                    if i > 0 {
7372                        self.write(",");
7373                        self.write_newline();
7374                    }
7375                    self.write_indent();
7376                    self.generate_table_constraint(constraint)?;
7377                }
7378                self.indent_level -= 1;
7379                self.write_newline();
7380                self.write(")");
7381            } else {
7382                if use_parens {
7383                    self.write(" (");
7384                } else {
7385                    self.write_space();
7386                }
7387                for (i, constraint) in ct.constraints.iter().enumerate() {
7388                    if i > 0 {
7389                        self.write(", ");
7390                    }
7391                    self.generate_table_constraint(constraint)?;
7392                }
7393                if use_parens {
7394                    self.write(")");
7395                }
7396            }
7397        }
7398
7399        // TSQL ON filegroup or ON filegroup (partition_column) clause
7400        if let Some(ref on_prop) = ct.on_property {
7401            self.write(" ");
7402            self.write_keyword("ON");
7403            self.write(" ");
7404            self.generate_expression(&on_prop.this)?;
7405        }
7406
7407        // Output SchemaCommentProperty BEFORE WITH properties (Presto/Hive/Spark style)
7408        // For ClickHouse, SchemaCommentProperty goes after AS SELECT, handled later
7409        if !is_clickhouse {
7410            for prop in &ct.properties {
7411                if let Expression::SchemaCommentProperty(_) = prop {
7412                    if self.config.pretty {
7413                        self.write_newline();
7414                    } else {
7415                        self.write_space();
7416                    }
7417                    self.generate_expression(prop)?;
7418                }
7419            }
7420        }
7421
7422        // WITH properties (output after columns if columns exist, otherwise before AS)
7423        if !ct.with_properties.is_empty() {
7424            // Snowflake ICEBERG/DYNAMIC TABLE: output properties inline (space-separated, no WITH wrapper)
7425            let is_snowflake_special_table = matches!(
7426                self.config.dialect,
7427                Some(crate::dialects::DialectType::Snowflake)
7428            ) && (ct.table_modifier.as_deref() == Some("ICEBERG")
7429                || ct.table_modifier.as_deref() == Some("DYNAMIC"));
7430            if is_snowflake_special_table {
7431                for (key, value) in &ct.with_properties {
7432                    self.write_space();
7433                    self.write(key);
7434                    self.write("=");
7435                    self.write(value);
7436                }
7437            } else if self.config.pretty {
7438                self.write_newline();
7439                self.write_keyword("WITH");
7440                self.write(" (");
7441                self.write_newline();
7442                self.indent_level += 1;
7443                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
7444                    if i > 0 {
7445                        self.write(",");
7446                        self.write_newline();
7447                    }
7448                    self.write_indent();
7449                    self.write(key);
7450                    self.write("=");
7451                    self.write(value);
7452                }
7453                self.indent_level -= 1;
7454                self.write_newline();
7455                self.write(")");
7456            } else {
7457                self.write_space();
7458                self.write_keyword("WITH");
7459                self.write(" (");
7460                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
7461                    if i > 0 {
7462                        self.write(", ");
7463                    }
7464                    self.write(key);
7465                    self.write("=");
7466                    self.write(value);
7467                }
7468                self.write(")");
7469            }
7470        }
7471
7472        let (pre_as_properties, post_as_properties): (Vec<&Expression>, Vec<&Expression>) =
7473            if is_clickhouse && ct.as_select.is_some() {
7474                let mut pre = Vec::new();
7475                let mut post = Vec::new();
7476                for prop in &ct.properties {
7477                    if matches!(prop, Expression::SchemaCommentProperty(_)) {
7478                        post.push(prop);
7479                    } else {
7480                        pre.push(prop);
7481                    }
7482                }
7483                (pre, post)
7484            } else {
7485                (ct.properties.iter().collect(), Vec::new())
7486            };
7487
7488        // Table properties like DEFAULT COLLATE (BigQuery), OPTIONS (...), TBLPROPERTIES (...), or PROPERTIES (...)
7489        for prop in pre_as_properties {
7490            // SchemaCommentProperty was already output before WITH properties (except for ClickHouse)
7491            if !is_clickhouse && matches!(prop, Expression::SchemaCommentProperty(_)) {
7492                continue;
7493            }
7494            if self.config.pretty {
7495                self.write_newline();
7496            } else {
7497                self.write_space();
7498            }
7499            // BigQuery: Properties containing OPTIONS should be wrapped with OPTIONS (...)
7500            // Hive: Properties should be wrapped with TBLPROPERTIES (...)
7501            // Doris/StarRocks: Properties should be wrapped with PROPERTIES (...)
7502            if let Expression::Properties(props) = prop {
7503                let is_hive_dialect = matches!(
7504                    self.config.dialect,
7505                    Some(crate::dialects::DialectType::Hive)
7506                        | Some(crate::dialects::DialectType::Spark)
7507                        | Some(crate::dialects::DialectType::Databricks)
7508                        | Some(crate::dialects::DialectType::Athena)
7509                );
7510                let is_doris_starrocks = matches!(
7511                    self.config.dialect,
7512                    Some(crate::dialects::DialectType::Doris)
7513                        | Some(crate::dialects::DialectType::StarRocks)
7514                );
7515                if is_hive_dialect {
7516                    self.generate_tblproperties_clause(&props.expressions)?;
7517                } else if is_doris_starrocks {
7518                    self.generate_properties_clause(&props.expressions)?;
7519                } else {
7520                    self.generate_options_clause(&props.expressions)?;
7521                }
7522            } else {
7523                self.generate_expression(prop)?;
7524            }
7525        }
7526
7527        // Post-table properties like TSQL WITH(SYSTEM_VERSIONING=ON(...)) or Doris PROPERTIES
7528        for prop in &ct.post_table_properties {
7529            if let Expression::WithSystemVersioningProperty(ref svp) = prop {
7530                self.write(" WITH(");
7531                self.generate_system_versioning_content(svp)?;
7532                self.write(")");
7533            } else if let Expression::Properties(props) = prop {
7534                // Doris/StarRocks: PROPERTIES ('key'='value', ...) in post_table_properties
7535                let is_doris_starrocks = matches!(
7536                    self.config.dialect,
7537                    Some(crate::dialects::DialectType::Doris)
7538                        | Some(crate::dialects::DialectType::StarRocks)
7539                );
7540                self.write_space();
7541                if is_doris_starrocks {
7542                    self.generate_properties_clause(&props.expressions)?;
7543                } else {
7544                    self.generate_options_clause(&props.expressions)?;
7545                }
7546            } else {
7547                self.write_space();
7548                self.generate_expression(prop)?;
7549            }
7550        }
7551
7552        // StarRocks ROLLUP property: ROLLUP (r1(col1, col2), r2(col1))
7553        // Only output for StarRocks target
7554        if let Some(ref rollup) = ct.rollup {
7555            if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
7556                self.write_space();
7557                self.generate_rollup_property(rollup)?;
7558            }
7559        }
7560
7561        // MySQL table options (ENGINE=val, AUTO_INCREMENT=val, etc.)
7562        // Only output for MySQL-compatible dialects; strip for others during transpilation
7563        // COMMENT is also used by Hive/Spark so we selectively preserve it
7564        let is_mysql_compatible = matches!(
7565            self.config.dialect,
7566            Some(DialectType::MySQL)
7567                | Some(DialectType::SingleStore)
7568                | Some(DialectType::Doris)
7569                | Some(DialectType::StarRocks)
7570                | None
7571        );
7572        let is_hive_compatible = matches!(
7573            self.config.dialect,
7574            Some(DialectType::Hive)
7575                | Some(DialectType::Spark)
7576                | Some(DialectType::Databricks)
7577                | Some(DialectType::Athena)
7578        );
7579        let mysql_pretty_options =
7580            self.config.pretty && matches!(self.config.dialect, Some(DialectType::MySQL));
7581        for (key, value) in &ct.mysql_table_options {
7582            // Skip non-MySQL-specific options for non-MySQL targets
7583            let should_output = if is_mysql_compatible {
7584                true
7585            } else if is_hive_compatible && key == "COMMENT" {
7586                true // COMMENT is valid in Hive/Spark table definitions
7587            } else {
7588                false
7589            };
7590            if should_output {
7591                if mysql_pretty_options {
7592                    self.write_newline();
7593                    self.write_indent();
7594                } else {
7595                    self.write_space();
7596                }
7597                self.write_keyword(key);
7598                // StarRocks/Doris: COMMENT 'value' (no =), others: COMMENT='value'
7599                if key == "COMMENT" && !self.config.schema_comment_with_eq {
7600                    self.write_space();
7601                } else {
7602                    self.write("=");
7603                }
7604                self.write(value);
7605            }
7606        }
7607
7608        // Spark/Databricks: USING PARQUET for temporary tables that don't already have a storage format
7609        if ct.temporary
7610            && matches!(
7611                self.config.dialect,
7612                Some(DialectType::Spark) | Some(DialectType::Databricks)
7613            )
7614            && ct.as_select.is_none()
7615        {
7616            self.write_space();
7617            self.write_keyword("USING PARQUET");
7618        }
7619
7620        // PostgreSQL INHERITS clause
7621        if !ct.inherits.is_empty() {
7622            self.write_space();
7623            self.write_keyword("INHERITS");
7624            self.write(" (");
7625            for (i, parent) in ct.inherits.iter().enumerate() {
7626                if i > 0 {
7627                    self.write(", ");
7628                }
7629                self.generate_table(parent)?;
7630            }
7631            self.write(")");
7632        }
7633
7634        // CREATE TABLE AS SELECT
7635        if let Some(ref query) = ct.as_select {
7636            self.write_space();
7637            self.write_keyword("AS");
7638            self.write_space();
7639            if ct.as_select_parenthesized {
7640                self.write("(");
7641            }
7642            self.generate_expression(query)?;
7643            if ct.as_select_parenthesized {
7644                self.write(")");
7645            }
7646
7647            // Teradata: WITH DATA / WITH NO DATA
7648            if let Some(with_data) = ct.with_data {
7649                self.write_space();
7650                self.write_keyword("WITH");
7651                if !with_data {
7652                    self.write_space();
7653                    self.write_keyword("NO");
7654                }
7655                self.write_space();
7656                self.write_keyword("DATA");
7657            }
7658
7659            // Teradata: AND STATISTICS / AND NO STATISTICS
7660            if let Some(with_statistics) = ct.with_statistics {
7661                self.write_space();
7662                self.write_keyword("AND");
7663                if !with_statistics {
7664                    self.write_space();
7665                    self.write_keyword("NO");
7666                }
7667                self.write_space();
7668                self.write_keyword("STATISTICS");
7669            }
7670
7671            // Teradata: Index specifications
7672            for index in &ct.teradata_indexes {
7673                self.write_space();
7674                match index.kind {
7675                    TeradataIndexKind::NoPrimary => {
7676                        self.write_keyword("NO PRIMARY INDEX");
7677                    }
7678                    TeradataIndexKind::Primary => {
7679                        self.write_keyword("PRIMARY INDEX");
7680                    }
7681                    TeradataIndexKind::PrimaryAmp => {
7682                        self.write_keyword("PRIMARY AMP INDEX");
7683                    }
7684                    TeradataIndexKind::Unique => {
7685                        self.write_keyword("UNIQUE INDEX");
7686                    }
7687                    TeradataIndexKind::UniquePrimary => {
7688                        self.write_keyword("UNIQUE PRIMARY INDEX");
7689                    }
7690                    TeradataIndexKind::Secondary => {
7691                        self.write_keyword("INDEX");
7692                    }
7693                }
7694                // Output index name if present
7695                if let Some(ref name) = index.name {
7696                    self.write_space();
7697                    self.write(name);
7698                }
7699                // Output columns if present
7700                if !index.columns.is_empty() {
7701                    self.write(" (");
7702                    for (i, col) in index.columns.iter().enumerate() {
7703                        if i > 0 {
7704                            self.write(", ");
7705                        }
7706                        self.write(col);
7707                    }
7708                    self.write(")");
7709                }
7710            }
7711
7712            // Teradata: ON COMMIT behavior for volatile tables
7713            if let Some(ref on_commit) = ct.on_commit {
7714                self.write_space();
7715                self.write_keyword("ON COMMIT");
7716                self.write_space();
7717                match on_commit {
7718                    OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
7719                    OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
7720                }
7721            }
7722
7723            if !post_as_properties.is_empty() {
7724                for prop in post_as_properties {
7725                    self.write_space();
7726                    self.generate_expression(prop)?;
7727                }
7728            }
7729
7730            // Restore Athena Hive context before early return
7731            self.athena_hive_context = saved_athena_hive_context;
7732            return Ok(());
7733        }
7734
7735        // ON COMMIT behavior (for non-CTAS tables)
7736        if let Some(ref on_commit) = ct.on_commit {
7737            self.write_space();
7738            self.write_keyword("ON COMMIT");
7739            self.write_space();
7740            match on_commit {
7741                OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
7742                OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
7743            }
7744        }
7745
7746        // Restore Athena Hive context
7747        self.athena_hive_context = saved_athena_hive_context;
7748
7749        Ok(())
7750    }
7751
7752    /// Generate column definition as an expression (for ROWS FROM alias columns, XMLTABLE/JSON_TABLE)
7753    /// Outputs: "col_name" TYPE [PATH 'xpath'] (not the full CREATE TABLE column definition)
7754    fn generate_column_def_expr(&mut self, col: &ColumnDef) -> Result<()> {
7755        // Output column name
7756        self.generate_identifier(&col.name)?;
7757        // Output data type if known
7758        if !matches!(col.data_type, DataType::Unknown) {
7759            self.write_space();
7760            self.generate_data_type(&col.data_type)?;
7761        }
7762        // Output PATH constraint if present (for XMLTABLE/JSON_TABLE columns)
7763        for constraint in &col.constraints {
7764            if let ColumnConstraint::Path(path_expr) = constraint {
7765                self.write_space();
7766                self.write_keyword("PATH");
7767                self.write_space();
7768                self.generate_expression(path_expr)?;
7769            }
7770        }
7771        Ok(())
7772    }
7773
7774    fn generate_column_def(&mut self, col: &ColumnDef) -> Result<()> {
7775        // Check if this is a TSQL computed column (no data type)
7776        let has_computed_no_type = matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
7777            && col
7778                .constraints
7779                .iter()
7780                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
7781        // Some dialects (notably TSQL/Fabric) do not include an explicit type for computed columns.
7782        let omit_computed_type = !self.config.computed_column_with_type
7783            && col
7784                .constraints
7785                .iter()
7786                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
7787
7788        // Check if this is a partition column spec (no data type, type is Unknown)
7789        // This is used in PostgreSQL PARTITION OF syntax where columns only have constraints
7790        let is_partition_column_spec = matches!(col.data_type, DataType::Unknown);
7791
7792        // Check if this is a DYNAMIC TABLE column (no data type, empty Custom name, no constraints)
7793        // Also check the no_type flag for SQLite columns without types
7794        let has_no_type = col.no_type
7795            || (matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
7796                && col.constraints.is_empty());
7797
7798        self.generate_identifier(&col.name)?;
7799
7800        // Check for SERIAL/BIGSERIAL/SMALLSERIAL expansion for Materialize and PostgreSQL
7801        let serial_expansion = if matches!(
7802            self.config.dialect,
7803            Some(DialectType::Materialize) | Some(DialectType::PostgreSQL)
7804        ) {
7805            if let DataType::Custom { ref name } = col.data_type {
7806                match name.to_uppercase().as_str() {
7807                    "SERIAL" => Some("INT"),
7808                    "BIGSERIAL" => Some("BIGINT"),
7809                    "SMALLSERIAL" => Some("SMALLINT"),
7810                    _ => None,
7811                }
7812            } else {
7813                None
7814            }
7815        } else {
7816            None
7817        };
7818
7819        if !has_computed_no_type && !omit_computed_type && !is_partition_column_spec && !has_no_type
7820        {
7821            self.write_space();
7822            // ClickHouse CREATE TABLE column types: suppress automatic Nullable wrapping
7823            // since ClickHouse uses explicit Nullable() in its type system.
7824            let saved_nullable_depth = self.clickhouse_nullable_depth;
7825            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
7826                self.clickhouse_nullable_depth = -1;
7827            }
7828            if let Some(int_type) = serial_expansion {
7829                // SERIAL -> INT (+ constraints added below)
7830                self.write_keyword(int_type);
7831            } else if col.unsigned && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
7832                // For DuckDB: convert unsigned integer types to their unsigned equivalents
7833                let unsigned_type = match &col.data_type {
7834                    DataType::Int { .. } => Some("UINTEGER"),
7835                    DataType::BigInt { .. } => Some("UBIGINT"),
7836                    DataType::SmallInt { .. } => Some("USMALLINT"),
7837                    DataType::TinyInt { .. } => Some("UTINYINT"),
7838                    _ => None,
7839                };
7840                if let Some(utype) = unsigned_type {
7841                    self.write_keyword(utype);
7842                } else {
7843                    self.generate_data_type(&col.data_type)?;
7844                }
7845            } else {
7846                self.generate_data_type(&col.data_type)?;
7847            }
7848            self.clickhouse_nullable_depth = saved_nullable_depth;
7849        }
7850
7851        // MySQL type modifiers (must come right after data type)
7852        // Skip UNSIGNED for DuckDB (already mapped to unsigned type above)
7853        if col.unsigned && !matches!(self.config.dialect, Some(DialectType::DuckDB)) {
7854            self.write_space();
7855            self.write_keyword("UNSIGNED");
7856        }
7857        if col.zerofill {
7858            self.write_space();
7859            self.write_keyword("ZEROFILL");
7860        }
7861
7862        // Teradata column attributes (must come right after data type, in specific order)
7863        // ORDER: CHARACTER SET, UPPERCASE, CASESPECIFIC, FORMAT, TITLE, INLINE LENGTH, COMPRESS
7864
7865        if let Some(ref charset) = col.character_set {
7866            self.write_space();
7867            self.write_keyword("CHARACTER SET");
7868            self.write_space();
7869            self.write(charset);
7870        }
7871
7872        if col.uppercase {
7873            self.write_space();
7874            self.write_keyword("UPPERCASE");
7875        }
7876
7877        if let Some(casespecific) = col.casespecific {
7878            self.write_space();
7879            if casespecific {
7880                self.write_keyword("CASESPECIFIC");
7881            } else {
7882                self.write_keyword("NOT CASESPECIFIC");
7883            }
7884        }
7885
7886        if let Some(ref format) = col.format {
7887            self.write_space();
7888            self.write_keyword("FORMAT");
7889            self.write(" '");
7890            self.write(format);
7891            self.write("'");
7892        }
7893
7894        if let Some(ref title) = col.title {
7895            self.write_space();
7896            self.write_keyword("TITLE");
7897            self.write(" '");
7898            self.write(title);
7899            self.write("'");
7900        }
7901
7902        if let Some(length) = col.inline_length {
7903            self.write_space();
7904            self.write_keyword("INLINE LENGTH");
7905            self.write(" ");
7906            self.write(&length.to_string());
7907        }
7908
7909        if let Some(ref compress) = col.compress {
7910            self.write_space();
7911            self.write_keyword("COMPRESS");
7912            if !compress.is_empty() {
7913                // Single string literal: output without parentheses (Teradata syntax)
7914                if compress.len() == 1 {
7915                    if let Expression::Literal(Literal::String(_)) = &compress[0] {
7916                        self.write_space();
7917                        self.generate_expression(&compress[0])?;
7918                    } else {
7919                        self.write(" (");
7920                        self.generate_expression(&compress[0])?;
7921                        self.write(")");
7922                    }
7923                } else {
7924                    self.write(" (");
7925                    for (i, val) in compress.iter().enumerate() {
7926                        if i > 0 {
7927                            self.write(", ");
7928                        }
7929                        self.generate_expression(val)?;
7930                    }
7931                    self.write(")");
7932                }
7933            }
7934        }
7935
7936        // Column constraints - output in original order if constraint_order is populated
7937        // Otherwise fall back to legacy fixed order for backward compatibility
7938        if !col.constraint_order.is_empty() {
7939            // Use constraint_order for original ordering
7940            // Track indices for constraints stored in the constraints Vec
7941            let mut references_idx = 0;
7942            let mut check_idx = 0;
7943            let mut generated_idx = 0;
7944            let mut collate_idx = 0;
7945            let mut comment_idx = 0;
7946            // The preprocessing in dialects/mod.rs now handles the correct ordering of
7947            // NOT NULL relative to IDENTITY for PostgreSQL, so no deferral needed here.
7948            let defer_not_null_after_identity = false;
7949            let mut pending_not_null_after_identity = false;
7950
7951            for constraint_type in &col.constraint_order {
7952                match constraint_type {
7953                    ConstraintType::PrimaryKey => {
7954                        // Materialize doesn't support PRIMARY KEY column constraints
7955                        if col.primary_key
7956                            && !matches!(self.config.dialect, Some(DialectType::Materialize))
7957                        {
7958                            if let Some(ref cname) = col.primary_key_constraint_name {
7959                                self.write_space();
7960                                self.write_keyword("CONSTRAINT");
7961                                self.write_space();
7962                                self.write(cname);
7963                            }
7964                            self.write_space();
7965                            self.write_keyword("PRIMARY KEY");
7966                            if let Some(ref order) = col.primary_key_order {
7967                                self.write_space();
7968                                match order {
7969                                    SortOrder::Asc => self.write_keyword("ASC"),
7970                                    SortOrder::Desc => self.write_keyword("DESC"),
7971                                }
7972                            }
7973                        }
7974                    }
7975                    ConstraintType::Unique => {
7976                        if col.unique {
7977                            if let Some(ref cname) = col.unique_constraint_name {
7978                                self.write_space();
7979                                self.write_keyword("CONSTRAINT");
7980                                self.write_space();
7981                                self.write(cname);
7982                            }
7983                            self.write_space();
7984                            self.write_keyword("UNIQUE");
7985                            // PostgreSQL 15+: NULLS NOT DISTINCT
7986                            if col.unique_nulls_not_distinct {
7987                                self.write(" NULLS NOT DISTINCT");
7988                            }
7989                        }
7990                    }
7991                    ConstraintType::NotNull => {
7992                        if col.nullable == Some(false) {
7993                            if defer_not_null_after_identity {
7994                                pending_not_null_after_identity = true;
7995                                continue;
7996                            }
7997                            if let Some(ref cname) = col.not_null_constraint_name {
7998                                self.write_space();
7999                                self.write_keyword("CONSTRAINT");
8000                                self.write_space();
8001                                self.write(cname);
8002                            }
8003                            self.write_space();
8004                            self.write_keyword("NOT NULL");
8005                        }
8006                    }
8007                    ConstraintType::Null => {
8008                        if col.nullable == Some(true) {
8009                            self.write_space();
8010                            self.write_keyword("NULL");
8011                        }
8012                    }
8013                    ConstraintType::Default => {
8014                        if let Some(ref default) = col.default {
8015                            self.write_space();
8016                            self.write_keyword("DEFAULT");
8017                            self.write_space();
8018                            self.generate_expression(default)?;
8019                        }
8020                    }
8021                    ConstraintType::AutoIncrement => {
8022                        if col.auto_increment {
8023                            // DuckDB doesn't support AUTO_INCREMENT - skip entirely
8024                            if matches!(
8025                                self.config.dialect,
8026                                Some(crate::dialects::DialectType::DuckDB)
8027                            ) {
8028                                // Skip - DuckDB uses sequences or rowid instead
8029                            } else if matches!(
8030                                self.config.dialect,
8031                                Some(crate::dialects::DialectType::Materialize)
8032                            ) {
8033                                // Materialize strips AUTO_INCREMENT but adds NOT NULL
8034                                if !matches!(col.nullable, Some(false)) {
8035                                    self.write_space();
8036                                    self.write_keyword("NOT NULL");
8037                                }
8038                            } else if matches!(
8039                                self.config.dialect,
8040                                Some(crate::dialects::DialectType::PostgreSQL)
8041                            ) {
8042                                // PostgreSQL: AUTO_INCREMENT -> GENERATED BY DEFAULT AS IDENTITY
8043                                self.write_space();
8044                                self.generate_auto_increment_keyword(col)?;
8045                            } else {
8046                                self.write_space();
8047                                self.generate_auto_increment_keyword(col)?;
8048                                if pending_not_null_after_identity {
8049                                    self.write_space();
8050                                    self.write_keyword("NOT NULL");
8051                                    pending_not_null_after_identity = false;
8052                                }
8053                            }
8054                        } // close else for DuckDB skip
8055                    }
8056                    ConstraintType::References => {
8057                        // Find next References constraint
8058                        while references_idx < col.constraints.len() {
8059                            if let ColumnConstraint::References(fk_ref) =
8060                                &col.constraints[references_idx]
8061                            {
8062                                // CONSTRAINT name if present
8063                                if let Some(ref name) = fk_ref.constraint_name {
8064                                    self.write_space();
8065                                    self.write_keyword("CONSTRAINT");
8066                                    self.write_space();
8067                                    self.write(name);
8068                                }
8069                                self.write_space();
8070                                if fk_ref.has_foreign_key_keywords {
8071                                    self.write_keyword("FOREIGN KEY");
8072                                    self.write_space();
8073                                }
8074                                self.write_keyword("REFERENCES");
8075                                self.write_space();
8076                                self.generate_table(&fk_ref.table)?;
8077                                if !fk_ref.columns.is_empty() {
8078                                    self.write(" (");
8079                                    for (i, c) in fk_ref.columns.iter().enumerate() {
8080                                        if i > 0 {
8081                                            self.write(", ");
8082                                        }
8083                                        self.generate_identifier(c)?;
8084                                    }
8085                                    self.write(")");
8086                                }
8087                                self.generate_referential_actions(fk_ref)?;
8088                                references_idx += 1;
8089                                break;
8090                            }
8091                            references_idx += 1;
8092                        }
8093                    }
8094                    ConstraintType::Check => {
8095                        // Find next Check constraint
8096                        while check_idx < col.constraints.len() {
8097                            if let ColumnConstraint::Check(expr) = &col.constraints[check_idx] {
8098                                // Output CONSTRAINT name if present (only for first CHECK)
8099                                if check_idx == 0 {
8100                                    if let Some(ref cname) = col.check_constraint_name {
8101                                        self.write_space();
8102                                        self.write_keyword("CONSTRAINT");
8103                                        self.write_space();
8104                                        self.write(cname);
8105                                    }
8106                                }
8107                                self.write_space();
8108                                self.write_keyword("CHECK");
8109                                self.write(" (");
8110                                self.generate_expression(expr)?;
8111                                self.write(")");
8112                                check_idx += 1;
8113                                break;
8114                            }
8115                            check_idx += 1;
8116                        }
8117                    }
8118                    ConstraintType::GeneratedAsIdentity => {
8119                        // Find next GeneratedAsIdentity constraint
8120                        while generated_idx < col.constraints.len() {
8121                            if let ColumnConstraint::GeneratedAsIdentity(gen) =
8122                                &col.constraints[generated_idx]
8123                            {
8124                                self.write_space();
8125                                // Redshift uses IDENTITY(start, increment) syntax
8126                                if matches!(
8127                                    self.config.dialect,
8128                                    Some(crate::dialects::DialectType::Redshift)
8129                                ) {
8130                                    self.write_keyword("IDENTITY");
8131                                    self.write("(");
8132                                    if let Some(ref start) = gen.start {
8133                                        self.generate_expression(start)?;
8134                                    } else {
8135                                        self.write("0");
8136                                    }
8137                                    self.write(", ");
8138                                    if let Some(ref incr) = gen.increment {
8139                                        self.generate_expression(incr)?;
8140                                    } else {
8141                                        self.write("1");
8142                                    }
8143                                    self.write(")");
8144                                } else {
8145                                    self.write_keyword("GENERATED");
8146                                    if gen.always {
8147                                        self.write_space();
8148                                        self.write_keyword("ALWAYS");
8149                                    } else {
8150                                        self.write_space();
8151                                        self.write_keyword("BY DEFAULT");
8152                                        if gen.on_null {
8153                                            self.write_space();
8154                                            self.write_keyword("ON NULL");
8155                                        }
8156                                    }
8157                                    self.write_space();
8158                                    self.write_keyword("AS IDENTITY");
8159
8160                                    let has_options = gen.start.is_some()
8161                                        || gen.increment.is_some()
8162                                        || gen.minvalue.is_some()
8163                                        || gen.maxvalue.is_some()
8164                                        || gen.cycle.is_some();
8165                                    if has_options {
8166                                        self.write(" (");
8167                                        let mut first = true;
8168                                        if let Some(ref start) = gen.start {
8169                                            if !first {
8170                                                self.write(" ");
8171                                            }
8172                                            first = false;
8173                                            self.write_keyword("START WITH");
8174                                            self.write_space();
8175                                            self.generate_expression(start)?;
8176                                        }
8177                                        if let Some(ref incr) = gen.increment {
8178                                            if !first {
8179                                                self.write(" ");
8180                                            }
8181                                            first = false;
8182                                            self.write_keyword("INCREMENT BY");
8183                                            self.write_space();
8184                                            self.generate_expression(incr)?;
8185                                        }
8186                                        if let Some(ref minv) = gen.minvalue {
8187                                            if !first {
8188                                                self.write(" ");
8189                                            }
8190                                            first = false;
8191                                            self.write_keyword("MINVALUE");
8192                                            self.write_space();
8193                                            self.generate_expression(minv)?;
8194                                        }
8195                                        if let Some(ref maxv) = gen.maxvalue {
8196                                            if !first {
8197                                                self.write(" ");
8198                                            }
8199                                            first = false;
8200                                            self.write_keyword("MAXVALUE");
8201                                            self.write_space();
8202                                            self.generate_expression(maxv)?;
8203                                        }
8204                                        if let Some(cycle) = gen.cycle {
8205                                            if !first {
8206                                                self.write(" ");
8207                                            }
8208                                            if cycle {
8209                                                self.write_keyword("CYCLE");
8210                                            } else {
8211                                                self.write_keyword("NO CYCLE");
8212                                            }
8213                                        }
8214                                        self.write(")");
8215                                    }
8216                                }
8217                                generated_idx += 1;
8218                                break;
8219                            }
8220                            generated_idx += 1;
8221                        }
8222                    }
8223                    ConstraintType::Collate => {
8224                        // Find next Collate constraint
8225                        while collate_idx < col.constraints.len() {
8226                            if let ColumnConstraint::Collate(collation) =
8227                                &col.constraints[collate_idx]
8228                            {
8229                                self.write_space();
8230                                self.write_keyword("COLLATE");
8231                                self.write_space();
8232                                self.generate_identifier(collation)?;
8233                                collate_idx += 1;
8234                                break;
8235                            }
8236                            collate_idx += 1;
8237                        }
8238                    }
8239                    ConstraintType::Comment => {
8240                        // Find next Comment constraint
8241                        while comment_idx < col.constraints.len() {
8242                            if let ColumnConstraint::Comment(comment) =
8243                                &col.constraints[comment_idx]
8244                            {
8245                                self.write_space();
8246                                self.write_keyword("COMMENT");
8247                                self.write_space();
8248                                self.generate_string_literal(comment)?;
8249                                comment_idx += 1;
8250                                break;
8251                            }
8252                            comment_idx += 1;
8253                        }
8254                    }
8255                    ConstraintType::Tags => {
8256                        // Find next Tags constraint (Snowflake)
8257                        for constraint in &col.constraints {
8258                            if let ColumnConstraint::Tags(tags) = constraint {
8259                                self.write_space();
8260                                self.write_keyword("TAG");
8261                                self.write(" (");
8262                                for (i, expr) in tags.expressions.iter().enumerate() {
8263                                    if i > 0 {
8264                                        self.write(", ");
8265                                    }
8266                                    self.generate_expression(expr)?;
8267                                }
8268                                self.write(")");
8269                                break;
8270                            }
8271                        }
8272                    }
8273                    ConstraintType::ComputedColumn => {
8274                        // Find next ComputedColumn constraint
8275                        for constraint in &col.constraints {
8276                            if let ColumnConstraint::ComputedColumn(cc) = constraint {
8277                                self.write_space();
8278                                self.generate_computed_column_inline(cc)?;
8279                                break;
8280                            }
8281                        }
8282                    }
8283                    ConstraintType::GeneratedAsRow => {
8284                        // Find next GeneratedAsRow constraint
8285                        for constraint in &col.constraints {
8286                            if let ColumnConstraint::GeneratedAsRow(gar) = constraint {
8287                                self.write_space();
8288                                self.generate_generated_as_row_inline(gar)?;
8289                                break;
8290                            }
8291                        }
8292                    }
8293                    ConstraintType::OnUpdate => {
8294                        if let Some(ref expr) = col.on_update {
8295                            self.write_space();
8296                            self.write_keyword("ON UPDATE");
8297                            self.write_space();
8298                            self.generate_expression(expr)?;
8299                        }
8300                    }
8301                    ConstraintType::Encode => {
8302                        if let Some(ref encoding) = col.encoding {
8303                            self.write_space();
8304                            self.write_keyword("ENCODE");
8305                            self.write_space();
8306                            self.write(encoding);
8307                        }
8308                    }
8309                    ConstraintType::Path => {
8310                        // Find next Path constraint
8311                        for constraint in &col.constraints {
8312                            if let ColumnConstraint::Path(path_expr) = constraint {
8313                                self.write_space();
8314                                self.write_keyword("PATH");
8315                                self.write_space();
8316                                self.generate_expression(path_expr)?;
8317                                break;
8318                            }
8319                        }
8320                    }
8321                }
8322            }
8323            if pending_not_null_after_identity {
8324                self.write_space();
8325                self.write_keyword("NOT NULL");
8326            }
8327        } else {
8328            // Legacy fixed order for backward compatibility
8329            if col.primary_key {
8330                self.write_space();
8331                self.write_keyword("PRIMARY KEY");
8332                if let Some(ref order) = col.primary_key_order {
8333                    self.write_space();
8334                    match order {
8335                        SortOrder::Asc => self.write_keyword("ASC"),
8336                        SortOrder::Desc => self.write_keyword("DESC"),
8337                    }
8338                }
8339            }
8340
8341            if col.unique {
8342                self.write_space();
8343                self.write_keyword("UNIQUE");
8344                // PostgreSQL 15+: NULLS NOT DISTINCT
8345                if col.unique_nulls_not_distinct {
8346                    self.write(" NULLS NOT DISTINCT");
8347                }
8348            }
8349
8350            match col.nullable {
8351                Some(false) => {
8352                    self.write_space();
8353                    self.write_keyword("NOT NULL");
8354                }
8355                Some(true) => {
8356                    self.write_space();
8357                    self.write_keyword("NULL");
8358                }
8359                None => {}
8360            }
8361
8362            if let Some(ref default) = col.default {
8363                self.write_space();
8364                self.write_keyword("DEFAULT");
8365                self.write_space();
8366                self.generate_expression(default)?;
8367            }
8368
8369            if col.auto_increment {
8370                self.write_space();
8371                self.generate_auto_increment_keyword(col)?;
8372            }
8373
8374            // Column-level constraints from Vec
8375            for constraint in &col.constraints {
8376                match constraint {
8377                    ColumnConstraint::References(fk_ref) => {
8378                        self.write_space();
8379                        if fk_ref.has_foreign_key_keywords {
8380                            self.write_keyword("FOREIGN KEY");
8381                            self.write_space();
8382                        }
8383                        self.write_keyword("REFERENCES");
8384                        self.write_space();
8385                        self.generate_table(&fk_ref.table)?;
8386                        if !fk_ref.columns.is_empty() {
8387                            self.write(" (");
8388                            for (i, c) in fk_ref.columns.iter().enumerate() {
8389                                if i > 0 {
8390                                    self.write(", ");
8391                                }
8392                                self.generate_identifier(c)?;
8393                            }
8394                            self.write(")");
8395                        }
8396                        self.generate_referential_actions(fk_ref)?;
8397                    }
8398                    ColumnConstraint::Check(expr) => {
8399                        self.write_space();
8400                        self.write_keyword("CHECK");
8401                        self.write(" (");
8402                        self.generate_expression(expr)?;
8403                        self.write(")");
8404                    }
8405                    ColumnConstraint::GeneratedAsIdentity(gen) => {
8406                        self.write_space();
8407                        // Redshift uses IDENTITY(start, increment) syntax
8408                        if matches!(
8409                            self.config.dialect,
8410                            Some(crate::dialects::DialectType::Redshift)
8411                        ) {
8412                            self.write_keyword("IDENTITY");
8413                            self.write("(");
8414                            if let Some(ref start) = gen.start {
8415                                self.generate_expression(start)?;
8416                            } else {
8417                                self.write("0");
8418                            }
8419                            self.write(", ");
8420                            if let Some(ref incr) = gen.increment {
8421                                self.generate_expression(incr)?;
8422                            } else {
8423                                self.write("1");
8424                            }
8425                            self.write(")");
8426                        } else {
8427                            self.write_keyword("GENERATED");
8428                            if gen.always {
8429                                self.write_space();
8430                                self.write_keyword("ALWAYS");
8431                            } else {
8432                                self.write_space();
8433                                self.write_keyword("BY DEFAULT");
8434                                if gen.on_null {
8435                                    self.write_space();
8436                                    self.write_keyword("ON NULL");
8437                                }
8438                            }
8439                            self.write_space();
8440                            self.write_keyword("AS IDENTITY");
8441
8442                            let has_options = gen.start.is_some()
8443                                || gen.increment.is_some()
8444                                || gen.minvalue.is_some()
8445                                || gen.maxvalue.is_some()
8446                                || gen.cycle.is_some();
8447                            if has_options {
8448                                self.write(" (");
8449                                let mut first = true;
8450                                if let Some(ref start) = gen.start {
8451                                    if !first {
8452                                        self.write(" ");
8453                                    }
8454                                    first = false;
8455                                    self.write_keyword("START WITH");
8456                                    self.write_space();
8457                                    self.generate_expression(start)?;
8458                                }
8459                                if let Some(ref incr) = gen.increment {
8460                                    if !first {
8461                                        self.write(" ");
8462                                    }
8463                                    first = false;
8464                                    self.write_keyword("INCREMENT BY");
8465                                    self.write_space();
8466                                    self.generate_expression(incr)?;
8467                                }
8468                                if let Some(ref minv) = gen.minvalue {
8469                                    if !first {
8470                                        self.write(" ");
8471                                    }
8472                                    first = false;
8473                                    self.write_keyword("MINVALUE");
8474                                    self.write_space();
8475                                    self.generate_expression(minv)?;
8476                                }
8477                                if let Some(ref maxv) = gen.maxvalue {
8478                                    if !first {
8479                                        self.write(" ");
8480                                    }
8481                                    first = false;
8482                                    self.write_keyword("MAXVALUE");
8483                                    self.write_space();
8484                                    self.generate_expression(maxv)?;
8485                                }
8486                                if let Some(cycle) = gen.cycle {
8487                                    if !first {
8488                                        self.write(" ");
8489                                    }
8490                                    if cycle {
8491                                        self.write_keyword("CYCLE");
8492                                    } else {
8493                                        self.write_keyword("NO CYCLE");
8494                                    }
8495                                }
8496                                self.write(")");
8497                            }
8498                        }
8499                    }
8500                    ColumnConstraint::Collate(collation) => {
8501                        self.write_space();
8502                        self.write_keyword("COLLATE");
8503                        self.write_space();
8504                        self.generate_identifier(collation)?;
8505                    }
8506                    ColumnConstraint::Comment(comment) => {
8507                        self.write_space();
8508                        self.write_keyword("COMMENT");
8509                        self.write_space();
8510                        self.generate_string_literal(comment)?;
8511                    }
8512                    ColumnConstraint::Path(path_expr) => {
8513                        self.write_space();
8514                        self.write_keyword("PATH");
8515                        self.write_space();
8516                        self.generate_expression(path_expr)?;
8517                    }
8518                    _ => {} // Other constraints handled above
8519                }
8520            }
8521
8522            // Redshift: ENCODE encoding_type (legacy path)
8523            if let Some(ref encoding) = col.encoding {
8524                self.write_space();
8525                self.write_keyword("ENCODE");
8526                self.write_space();
8527                self.write(encoding);
8528            }
8529        }
8530
8531        // ClickHouse: CODEC(...)
8532        if let Some(ref codec) = col.codec {
8533            self.write_space();
8534            self.write_keyword("CODEC");
8535            self.write("(");
8536            self.write(codec);
8537            self.write(")");
8538        }
8539
8540        // ClickHouse: EPHEMERAL [expr]
8541        if let Some(ref ephemeral) = col.ephemeral {
8542            self.write_space();
8543            self.write_keyword("EPHEMERAL");
8544            if let Some(ref expr) = ephemeral {
8545                self.write_space();
8546                self.generate_expression(expr)?;
8547            }
8548        }
8549
8550        // ClickHouse: MATERIALIZED expr
8551        if let Some(ref mat_expr) = col.materialized_expr {
8552            self.write_space();
8553            self.write_keyword("MATERIALIZED");
8554            self.write_space();
8555            self.generate_expression(mat_expr)?;
8556        }
8557
8558        // ClickHouse: ALIAS expr
8559        if let Some(ref alias_expr) = col.alias_expr {
8560            self.write_space();
8561            self.write_keyword("ALIAS");
8562            self.write_space();
8563            self.generate_expression(alias_expr)?;
8564        }
8565
8566        // ClickHouse: TTL expr
8567        if let Some(ref ttl_expr) = col.ttl_expr {
8568            self.write_space();
8569            self.write_keyword("TTL");
8570            self.write_space();
8571            self.generate_expression(ttl_expr)?;
8572        }
8573
8574        // TSQL: NOT FOR REPLICATION
8575        if col.not_for_replication
8576            && matches!(
8577                self.config.dialect,
8578                Some(crate::dialects::DialectType::TSQL)
8579                    | Some(crate::dialects::DialectType::Fabric)
8580            )
8581        {
8582            self.write_space();
8583            self.write_keyword("NOT FOR REPLICATION");
8584        }
8585
8586        // BigQuery: OPTIONS (key=value, ...) on column - comes after all constraints
8587        if !col.options.is_empty() {
8588            self.write_space();
8589            self.generate_options_clause(&col.options)?;
8590        }
8591
8592        // SQLite: Inline PRIMARY KEY from table constraint
8593        // This comes at the end, after all existing column constraints
8594        if !col.primary_key
8595            && self
8596                .sqlite_inline_pk_columns
8597                .contains(&col.name.name.to_lowercase())
8598        {
8599            self.write_space();
8600            self.write_keyword("PRIMARY KEY");
8601        }
8602
8603        // SERIAL expansion: add GENERATED BY DEFAULT AS IDENTITY NOT NULL for PostgreSQL,
8604        // just NOT NULL for Materialize (which strips GENERATED AS IDENTITY)
8605        if serial_expansion.is_some() {
8606            if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
8607                self.write_space();
8608                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY NOT NULL");
8609            } else if matches!(self.config.dialect, Some(DialectType::Materialize)) {
8610                self.write_space();
8611                self.write_keyword("NOT NULL");
8612            }
8613        }
8614
8615        Ok(())
8616    }
8617
8618    fn generate_table_constraint(&mut self, constraint: &TableConstraint) -> Result<()> {
8619        match constraint {
8620            TableConstraint::PrimaryKey {
8621                name,
8622                columns,
8623                include_columns,
8624                modifiers,
8625                has_constraint_keyword,
8626            } => {
8627                if let Some(ref n) = name {
8628                    if *has_constraint_keyword {
8629                        self.write_keyword("CONSTRAINT");
8630                        self.write_space();
8631                        self.generate_identifier(n)?;
8632                        self.write_space();
8633                    }
8634                }
8635                self.write_keyword("PRIMARY KEY");
8636                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
8637                if let Some(ref clustered) = modifiers.clustered {
8638                    self.write_space();
8639                    self.write_keyword(clustered);
8640                }
8641                // MySQL format: PRIMARY KEY name (cols) when no CONSTRAINT keyword
8642                if let Some(ref n) = name {
8643                    if !*has_constraint_keyword {
8644                        self.write_space();
8645                        self.generate_identifier(n)?;
8646                    }
8647                }
8648                self.write(" (");
8649                for (i, col) in columns.iter().enumerate() {
8650                    if i > 0 {
8651                        self.write(", ");
8652                    }
8653                    self.generate_identifier(col)?;
8654                }
8655                self.write(")");
8656                if !include_columns.is_empty() {
8657                    self.write_space();
8658                    self.write_keyword("INCLUDE");
8659                    self.write(" (");
8660                    for (i, col) in include_columns.iter().enumerate() {
8661                        if i > 0 {
8662                            self.write(", ");
8663                        }
8664                        self.generate_identifier(col)?;
8665                    }
8666                    self.write(")");
8667                }
8668                self.generate_constraint_modifiers(modifiers);
8669            }
8670            TableConstraint::Unique {
8671                name,
8672                columns,
8673                columns_parenthesized,
8674                modifiers,
8675                has_constraint_keyword,
8676                nulls_not_distinct,
8677            } => {
8678                if let Some(ref n) = name {
8679                    if *has_constraint_keyword {
8680                        self.write_keyword("CONSTRAINT");
8681                        self.write_space();
8682                        self.generate_identifier(n)?;
8683                        self.write_space();
8684                    }
8685                }
8686                self.write_keyword("UNIQUE");
8687                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
8688                if let Some(ref clustered) = modifiers.clustered {
8689                    self.write_space();
8690                    self.write_keyword(clustered);
8691                }
8692                // PostgreSQL 15+: NULLS NOT DISTINCT
8693                if *nulls_not_distinct {
8694                    self.write(" NULLS NOT DISTINCT");
8695                }
8696                // MySQL format: UNIQUE name (cols) when no CONSTRAINT keyword
8697                if let Some(ref n) = name {
8698                    if !*has_constraint_keyword {
8699                        self.write_space();
8700                        self.generate_identifier(n)?;
8701                    }
8702                }
8703                if *columns_parenthesized {
8704                    self.write(" (");
8705                    for (i, col) in columns.iter().enumerate() {
8706                        if i > 0 {
8707                            self.write(", ");
8708                        }
8709                        self.generate_identifier(col)?;
8710                    }
8711                    self.write(")");
8712                } else {
8713                    // UNIQUE without parentheses (e.g., UNIQUE idx_name)
8714                    for col in columns.iter() {
8715                        self.write_space();
8716                        self.generate_identifier(col)?;
8717                    }
8718                }
8719                self.generate_constraint_modifiers(modifiers);
8720            }
8721            TableConstraint::ForeignKey {
8722                name,
8723                columns,
8724                references,
8725                on_delete,
8726                on_update,
8727                modifiers,
8728            } => {
8729                if let Some(ref n) = name {
8730                    self.write_keyword("CONSTRAINT");
8731                    self.write_space();
8732                    self.generate_identifier(n)?;
8733                    self.write_space();
8734                }
8735                self.write_keyword("FOREIGN KEY");
8736                self.write(" (");
8737                for (i, col) in columns.iter().enumerate() {
8738                    if i > 0 {
8739                        self.write(", ");
8740                    }
8741                    self.generate_identifier(col)?;
8742                }
8743                self.write(")");
8744                if let Some(ref refs) = references {
8745                    self.write(" ");
8746                    self.write_keyword("REFERENCES");
8747                    self.write_space();
8748                    self.generate_table(&refs.table)?;
8749                    if !refs.columns.is_empty() {
8750                        if self.config.pretty {
8751                            self.write(" (");
8752                            self.write_newline();
8753                            self.indent_level += 1;
8754                            for (i, col) in refs.columns.iter().enumerate() {
8755                                if i > 0 {
8756                                    self.write(",");
8757                                    self.write_newline();
8758                                }
8759                                self.write_indent();
8760                                self.generate_identifier(col)?;
8761                            }
8762                            self.indent_level -= 1;
8763                            self.write_newline();
8764                            self.write_indent();
8765                            self.write(")");
8766                        } else {
8767                            self.write(" (");
8768                            for (i, col) in refs.columns.iter().enumerate() {
8769                                if i > 0 {
8770                                    self.write(", ");
8771                                }
8772                                self.generate_identifier(col)?;
8773                            }
8774                            self.write(")");
8775                        }
8776                    }
8777                    self.generate_referential_actions(refs)?;
8778                } else {
8779                    // No REFERENCES - output ON DELETE/ON UPDATE directly
8780                    if let Some(ref action) = on_delete {
8781                        self.write_space();
8782                        self.write_keyword("ON DELETE");
8783                        self.write_space();
8784                        self.generate_referential_action(action);
8785                    }
8786                    if let Some(ref action) = on_update {
8787                        self.write_space();
8788                        self.write_keyword("ON UPDATE");
8789                        self.write_space();
8790                        self.generate_referential_action(action);
8791                    }
8792                }
8793                self.generate_constraint_modifiers(modifiers);
8794            }
8795            TableConstraint::Check {
8796                name,
8797                expression,
8798                modifiers,
8799            } => {
8800                if let Some(ref n) = name {
8801                    self.write_keyword("CONSTRAINT");
8802                    self.write_space();
8803                    self.generate_identifier(n)?;
8804                    self.write_space();
8805                }
8806                self.write_keyword("CHECK");
8807                self.write(" (");
8808                self.generate_expression(expression)?;
8809                self.write(")");
8810                self.generate_constraint_modifiers(modifiers);
8811            }
8812            TableConstraint::Index {
8813                name,
8814                columns,
8815                kind,
8816                modifiers,
8817                use_key_keyword,
8818                expression,
8819                index_type,
8820                granularity,
8821            } => {
8822                // ClickHouse-style INDEX: INDEX name expr TYPE type_func GRANULARITY n
8823                if expression.is_some() {
8824                    self.write_keyword("INDEX");
8825                    if let Some(ref n) = name {
8826                        self.write_space();
8827                        self.generate_identifier(n)?;
8828                    }
8829                    if let Some(ref expr) = expression {
8830                        self.write_space();
8831                        self.generate_expression(expr)?;
8832                    }
8833                    if let Some(ref idx_type) = index_type {
8834                        self.write_space();
8835                        self.write_keyword("TYPE");
8836                        self.write_space();
8837                        self.generate_expression(idx_type)?;
8838                    }
8839                    if let Some(ref gran) = granularity {
8840                        self.write_space();
8841                        self.write_keyword("GRANULARITY");
8842                        self.write_space();
8843                        self.generate_expression(gran)?;
8844                    }
8845                } else {
8846                    // Standard INDEX syntax
8847                    // Determine the index keyword to use
8848                    // MySQL normalizes KEY to INDEX
8849                    use crate::dialects::DialectType;
8850                    let index_keyword = if *use_key_keyword
8851                        && !matches!(self.config.dialect, Some(DialectType::MySQL))
8852                    {
8853                        "KEY"
8854                    } else {
8855                        "INDEX"
8856                    };
8857
8858                    // Output kind (UNIQUE, FULLTEXT, SPATIAL) if present
8859                    if let Some(ref k) = kind {
8860                        self.write_keyword(k);
8861                        // For UNIQUE, don't add INDEX/KEY keyword
8862                        if k != "UNIQUE" {
8863                            self.write_space();
8864                            self.write_keyword(index_keyword);
8865                        }
8866                    } else {
8867                        self.write_keyword(index_keyword);
8868                    }
8869
8870                    // Output USING before name if using_before_columns is true and there's no name
8871                    if modifiers.using_before_columns && name.is_none() {
8872                        if let Some(ref using) = modifiers.using {
8873                            self.write_space();
8874                            self.write_keyword("USING");
8875                            self.write_space();
8876                            self.write_keyword(using);
8877                        }
8878                    }
8879
8880                    // Output index name if present
8881                    if let Some(ref n) = name {
8882                        self.write_space();
8883                        self.generate_identifier(n)?;
8884                    }
8885
8886                    // Output USING after name but before columns if using_before_columns and there's a name
8887                    if modifiers.using_before_columns && name.is_some() {
8888                        if let Some(ref using) = modifiers.using {
8889                            self.write_space();
8890                            self.write_keyword("USING");
8891                            self.write_space();
8892                            self.write_keyword(using);
8893                        }
8894                    }
8895
8896                    // Output columns
8897                    self.write(" (");
8898                    for (i, col) in columns.iter().enumerate() {
8899                        if i > 0 {
8900                            self.write(", ");
8901                        }
8902                        self.generate_identifier(col)?;
8903                    }
8904                    self.write(")");
8905
8906                    // Output USING after columns if not using_before_columns
8907                    if !modifiers.using_before_columns {
8908                        if let Some(ref using) = modifiers.using {
8909                            self.write_space();
8910                            self.write_keyword("USING");
8911                            self.write_space();
8912                            self.write_keyword(using);
8913                        }
8914                    }
8915
8916                    // Output other constraint modifiers (but skip USING since we already handled it)
8917                    self.generate_constraint_modifiers_without_using(modifiers);
8918                }
8919            }
8920            TableConstraint::Projection { name, expression } => {
8921                // ClickHouse: PROJECTION name (SELECT ...)
8922                self.write_keyword("PROJECTION");
8923                self.write_space();
8924                self.generate_identifier(name)?;
8925                self.write(" (");
8926                self.generate_expression(expression)?;
8927                self.write(")");
8928            }
8929            TableConstraint::Like { source, options } => {
8930                self.write_keyword("LIKE");
8931                self.write_space();
8932                self.generate_table(source)?;
8933                for (action, prop) in options {
8934                    self.write_space();
8935                    match action {
8936                        LikeOptionAction::Including => self.write_keyword("INCLUDING"),
8937                        LikeOptionAction::Excluding => self.write_keyword("EXCLUDING"),
8938                    }
8939                    self.write_space();
8940                    self.write_keyword(prop);
8941                }
8942            }
8943            TableConstraint::PeriodForSystemTime { start_col, end_col } => {
8944                self.write_keyword("PERIOD FOR SYSTEM_TIME");
8945                self.write(" (");
8946                self.generate_identifier(start_col)?;
8947                self.write(", ");
8948                self.generate_identifier(end_col)?;
8949                self.write(")");
8950            }
8951            TableConstraint::Exclude {
8952                name,
8953                using,
8954                elements,
8955                include_columns,
8956                where_clause,
8957                with_params,
8958                using_index_tablespace,
8959                modifiers: _,
8960            } => {
8961                if let Some(ref n) = name {
8962                    self.write_keyword("CONSTRAINT");
8963                    self.write_space();
8964                    self.generate_identifier(n)?;
8965                    self.write_space();
8966                }
8967                self.write_keyword("EXCLUDE");
8968                if let Some(ref method) = using {
8969                    self.write_space();
8970                    self.write_keyword("USING");
8971                    self.write_space();
8972                    self.write(method);
8973                    self.write("(");
8974                } else {
8975                    self.write(" (");
8976                }
8977                for (i, elem) in elements.iter().enumerate() {
8978                    if i > 0 {
8979                        self.write(", ");
8980                    }
8981                    self.write(&elem.expression);
8982                    self.write_space();
8983                    self.write_keyword("WITH");
8984                    self.write_space();
8985                    self.write(&elem.operator);
8986                }
8987                self.write(")");
8988                if !include_columns.is_empty() {
8989                    self.write_space();
8990                    self.write_keyword("INCLUDE");
8991                    self.write(" (");
8992                    for (i, col) in include_columns.iter().enumerate() {
8993                        if i > 0 {
8994                            self.write(", ");
8995                        }
8996                        self.generate_identifier(col)?;
8997                    }
8998                    self.write(")");
8999                }
9000                if !with_params.is_empty() {
9001                    self.write_space();
9002                    self.write_keyword("WITH");
9003                    self.write(" (");
9004                    for (i, (key, val)) in with_params.iter().enumerate() {
9005                        if i > 0 {
9006                            self.write(", ");
9007                        }
9008                        self.write(key);
9009                        self.write("=");
9010                        self.write(val);
9011                    }
9012                    self.write(")");
9013                }
9014                if let Some(ref tablespace) = using_index_tablespace {
9015                    self.write_space();
9016                    self.write_keyword("USING INDEX TABLESPACE");
9017                    self.write_space();
9018                    self.write(tablespace);
9019                }
9020                if let Some(ref where_expr) = where_clause {
9021                    self.write_space();
9022                    self.write_keyword("WHERE");
9023                    self.write(" (");
9024                    self.generate_expression(where_expr)?;
9025                    self.write(")");
9026                }
9027            }
9028            TableConstraint::Tags(tags) => {
9029                self.write_keyword("TAG");
9030                self.write(" (");
9031                for (i, expr) in tags.expressions.iter().enumerate() {
9032                    if i > 0 {
9033                        self.write(", ");
9034                    }
9035                    self.generate_expression(expr)?;
9036                }
9037                self.write(")");
9038            }
9039            TableConstraint::InitiallyDeferred { deferred } => {
9040                self.write_keyword("INITIALLY");
9041                self.write_space();
9042                if *deferred {
9043                    self.write_keyword("DEFERRED");
9044                } else {
9045                    self.write_keyword("IMMEDIATE");
9046                }
9047            }
9048        }
9049        Ok(())
9050    }
9051
9052    fn generate_constraint_modifiers(&mut self, modifiers: &ConstraintModifiers) {
9053        // Output USING BTREE/HASH (MySQL) - comes first
9054        if let Some(using) = &modifiers.using {
9055            self.write_space();
9056            self.write_keyword("USING");
9057            self.write_space();
9058            self.write_keyword(using);
9059        }
9060        // Output ENFORCED/NOT ENFORCED
9061        if let Some(enforced) = modifiers.enforced {
9062            self.write_space();
9063            if enforced {
9064                self.write_keyword("ENFORCED");
9065            } else {
9066                self.write_keyword("NOT ENFORCED");
9067            }
9068        }
9069        // Output DEFERRABLE/NOT DEFERRABLE
9070        if let Some(deferrable) = modifiers.deferrable {
9071            self.write_space();
9072            if deferrable {
9073                self.write_keyword("DEFERRABLE");
9074            } else {
9075                self.write_keyword("NOT DEFERRABLE");
9076            }
9077        }
9078        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
9079        if let Some(initially_deferred) = modifiers.initially_deferred {
9080            self.write_space();
9081            if initially_deferred {
9082                self.write_keyword("INITIALLY DEFERRED");
9083            } else {
9084                self.write_keyword("INITIALLY IMMEDIATE");
9085            }
9086        }
9087        // Output NORELY
9088        if modifiers.norely {
9089            self.write_space();
9090            self.write_keyword("NORELY");
9091        }
9092        // Output RELY
9093        if modifiers.rely {
9094            self.write_space();
9095            self.write_keyword("RELY");
9096        }
9097        // Output NOT VALID (PostgreSQL)
9098        if modifiers.not_valid {
9099            self.write_space();
9100            self.write_keyword("NOT VALID");
9101        }
9102        // Output ON CONFLICT (SQLite)
9103        if let Some(on_conflict) = &modifiers.on_conflict {
9104            self.write_space();
9105            self.write_keyword("ON CONFLICT");
9106            self.write_space();
9107            self.write_keyword(on_conflict);
9108        }
9109        // Output TSQL WITH options (PAD_INDEX=ON, STATISTICS_NORECOMPUTE=OFF, ...)
9110        if !modifiers.with_options.is_empty() {
9111            self.write_space();
9112            self.write_keyword("WITH");
9113            self.write(" (");
9114            for (i, (key, value)) in modifiers.with_options.iter().enumerate() {
9115                if i > 0 {
9116                    self.write(", ");
9117                }
9118                self.write(key);
9119                self.write("=");
9120                self.write(value);
9121            }
9122            self.write(")");
9123        }
9124        // Output TSQL ON filegroup
9125        if let Some(ref fg) = modifiers.on_filegroup {
9126            self.write_space();
9127            self.write_keyword("ON");
9128            self.write_space();
9129            let _ = self.generate_identifier(fg);
9130        }
9131    }
9132
9133    /// Generate constraint modifiers without USING (for Index constraints where USING is handled separately)
9134    fn generate_constraint_modifiers_without_using(&mut self, modifiers: &ConstraintModifiers) {
9135        // Output ENFORCED/NOT ENFORCED
9136        if let Some(enforced) = modifiers.enforced {
9137            self.write_space();
9138            if enforced {
9139                self.write_keyword("ENFORCED");
9140            } else {
9141                self.write_keyword("NOT ENFORCED");
9142            }
9143        }
9144        // Output DEFERRABLE/NOT DEFERRABLE
9145        if let Some(deferrable) = modifiers.deferrable {
9146            self.write_space();
9147            if deferrable {
9148                self.write_keyword("DEFERRABLE");
9149            } else {
9150                self.write_keyword("NOT DEFERRABLE");
9151            }
9152        }
9153        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
9154        if let Some(initially_deferred) = modifiers.initially_deferred {
9155            self.write_space();
9156            if initially_deferred {
9157                self.write_keyword("INITIALLY DEFERRED");
9158            } else {
9159                self.write_keyword("INITIALLY IMMEDIATE");
9160            }
9161        }
9162        // Output NORELY
9163        if modifiers.norely {
9164            self.write_space();
9165            self.write_keyword("NORELY");
9166        }
9167        // Output RELY
9168        if modifiers.rely {
9169            self.write_space();
9170            self.write_keyword("RELY");
9171        }
9172        // Output NOT VALID (PostgreSQL)
9173        if modifiers.not_valid {
9174            self.write_space();
9175            self.write_keyword("NOT VALID");
9176        }
9177        // Output ON CONFLICT (SQLite)
9178        if let Some(on_conflict) = &modifiers.on_conflict {
9179            self.write_space();
9180            self.write_keyword("ON CONFLICT");
9181            self.write_space();
9182            self.write_keyword(on_conflict);
9183        }
9184        // Output MySQL index-specific modifiers
9185        self.generate_index_specific_modifiers(modifiers);
9186    }
9187
9188    /// Generate MySQL index-specific modifiers (COMMENT, VISIBLE, ENGINE_ATTRIBUTE, WITH PARSER)
9189    fn generate_index_specific_modifiers(&mut self, modifiers: &ConstraintModifiers) {
9190        if let Some(ref comment) = modifiers.comment {
9191            self.write_space();
9192            self.write_keyword("COMMENT");
9193            self.write(" '");
9194            self.write(comment);
9195            self.write("'");
9196        }
9197        if let Some(visible) = modifiers.visible {
9198            self.write_space();
9199            if visible {
9200                self.write_keyword("VISIBLE");
9201            } else {
9202                self.write_keyword("INVISIBLE");
9203            }
9204        }
9205        if let Some(ref attr) = modifiers.engine_attribute {
9206            self.write_space();
9207            self.write_keyword("ENGINE_ATTRIBUTE");
9208            self.write(" = '");
9209            self.write(attr);
9210            self.write("'");
9211        }
9212        if let Some(ref parser) = modifiers.with_parser {
9213            self.write_space();
9214            self.write_keyword("WITH PARSER");
9215            self.write_space();
9216            self.write(parser);
9217        }
9218    }
9219
9220    fn generate_referential_actions(&mut self, fk_ref: &ForeignKeyRef) -> Result<()> {
9221        // MATCH clause before ON DELETE/ON UPDATE (default position, e.g. PostgreSQL)
9222        if !fk_ref.match_after_actions {
9223            if let Some(ref match_type) = fk_ref.match_type {
9224                self.write_space();
9225                self.write_keyword("MATCH");
9226                self.write_space();
9227                match match_type {
9228                    MatchType::Full => self.write_keyword("FULL"),
9229                    MatchType::Partial => self.write_keyword("PARTIAL"),
9230                    MatchType::Simple => self.write_keyword("SIMPLE"),
9231                }
9232            }
9233        }
9234
9235        // Output ON UPDATE and ON DELETE in the original order
9236        if fk_ref.on_update_first {
9237            if let Some(ref action) = fk_ref.on_update {
9238                self.write_space();
9239                self.write_keyword("ON UPDATE");
9240                self.write_space();
9241                self.generate_referential_action(action);
9242            }
9243            if let Some(ref action) = fk_ref.on_delete {
9244                self.write_space();
9245                self.write_keyword("ON DELETE");
9246                self.write_space();
9247                self.generate_referential_action(action);
9248            }
9249        } else {
9250            if let Some(ref action) = fk_ref.on_delete {
9251                self.write_space();
9252                self.write_keyword("ON DELETE");
9253                self.write_space();
9254                self.generate_referential_action(action);
9255            }
9256            if let Some(ref action) = fk_ref.on_update {
9257                self.write_space();
9258                self.write_keyword("ON UPDATE");
9259                self.write_space();
9260                self.generate_referential_action(action);
9261            }
9262        }
9263
9264        // MATCH clause after ON DELETE/ON UPDATE (when original SQL had it after)
9265        if fk_ref.match_after_actions {
9266            if let Some(ref match_type) = fk_ref.match_type {
9267                self.write_space();
9268                self.write_keyword("MATCH");
9269                self.write_space();
9270                match match_type {
9271                    MatchType::Full => self.write_keyword("FULL"),
9272                    MatchType::Partial => self.write_keyword("PARTIAL"),
9273                    MatchType::Simple => self.write_keyword("SIMPLE"),
9274                }
9275            }
9276        }
9277
9278        // DEFERRABLE / NOT DEFERRABLE
9279        if let Some(deferrable) = fk_ref.deferrable {
9280            self.write_space();
9281            if deferrable {
9282                self.write_keyword("DEFERRABLE");
9283            } else {
9284                self.write_keyword("NOT DEFERRABLE");
9285            }
9286        }
9287
9288        Ok(())
9289    }
9290
9291    fn generate_referential_action(&mut self, action: &ReferentialAction) {
9292        match action {
9293            ReferentialAction::Cascade => self.write_keyword("CASCADE"),
9294            ReferentialAction::SetNull => self.write_keyword("SET NULL"),
9295            ReferentialAction::SetDefault => self.write_keyword("SET DEFAULT"),
9296            ReferentialAction::Restrict => self.write_keyword("RESTRICT"),
9297            ReferentialAction::NoAction => self.write_keyword("NO ACTION"),
9298        }
9299    }
9300
9301    fn generate_drop_table(&mut self, dt: &DropTable) -> Result<()> {
9302        // Athena: DROP TABLE uses Hive engine (backticks)
9303        let saved_athena_hive_context = self.athena_hive_context;
9304        if matches!(
9305            self.config.dialect,
9306            Some(crate::dialects::DialectType::Athena)
9307        ) {
9308            self.athena_hive_context = true;
9309        }
9310
9311        // Output leading comments (e.g., "-- comment\nDROP TABLE ...")
9312        for comment in &dt.leading_comments {
9313            self.write_formatted_comment(comment);
9314            self.write_space();
9315        }
9316        self.write_keyword("DROP TABLE");
9317
9318        if dt.if_exists {
9319            self.write_space();
9320            self.write_keyword("IF EXISTS");
9321        }
9322
9323        self.write_space();
9324        for (i, table) in dt.names.iter().enumerate() {
9325            if i > 0 {
9326                self.write(", ");
9327            }
9328            self.generate_table(table)?;
9329        }
9330
9331        if dt.cascade_constraints {
9332            self.write_space();
9333            self.write_keyword("CASCADE CONSTRAINTS");
9334        } else if dt.cascade {
9335            self.write_space();
9336            self.write_keyword("CASCADE");
9337        }
9338
9339        if dt.purge {
9340            self.write_space();
9341            self.write_keyword("PURGE");
9342        }
9343
9344        // Restore Athena Hive context
9345        self.athena_hive_context = saved_athena_hive_context;
9346
9347        Ok(())
9348    }
9349
9350    fn generate_alter_table(&mut self, at: &AlterTable) -> Result<()> {
9351        // Athena: ALTER TABLE uses Hive engine (backticks)
9352        let saved_athena_hive_context = self.athena_hive_context;
9353        if matches!(
9354            self.config.dialect,
9355            Some(crate::dialects::DialectType::Athena)
9356        ) {
9357            self.athena_hive_context = true;
9358        }
9359
9360        self.write_keyword("ALTER TABLE");
9361        if at.if_exists {
9362            self.write_space();
9363            self.write_keyword("IF EXISTS");
9364        }
9365        self.write_space();
9366        self.generate_table(&at.name)?;
9367
9368        // ClickHouse: ON CLUSTER clause
9369        if let Some(ref on_cluster) = at.on_cluster {
9370            self.write_space();
9371            self.generate_on_cluster(on_cluster)?;
9372        }
9373
9374        // Hive: PARTITION(key=value, ...) clause
9375        if let Some(ref partition) = at.partition {
9376            self.write_space();
9377            self.write_keyword("PARTITION");
9378            self.write("(");
9379            for (i, (key, value)) in partition.iter().enumerate() {
9380                if i > 0 {
9381                    self.write(", ");
9382                }
9383                self.generate_identifier(key)?;
9384                self.write(" = ");
9385                self.generate_expression(value)?;
9386            }
9387            self.write(")");
9388        }
9389
9390        // TSQL: WITH CHECK / WITH NOCHECK modifier
9391        if let Some(ref with_check) = at.with_check {
9392            self.write_space();
9393            self.write_keyword(with_check);
9394        }
9395
9396        if self.config.pretty {
9397            // In pretty mode, format actions with newlines and indentation
9398            self.write_newline();
9399            self.indent_level += 1;
9400            for (i, action) in at.actions.iter().enumerate() {
9401                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
9402                let is_continuation = i > 0
9403                    && matches!(
9404                        (&at.actions[i - 1], action),
9405                        (
9406                            AlterTableAction::AddColumn { .. },
9407                            AlterTableAction::AddColumn { .. }
9408                        ) | (
9409                            AlterTableAction::AddConstraint(_),
9410                            AlterTableAction::AddConstraint(_)
9411                        )
9412                    );
9413                if i > 0 {
9414                    self.write(",");
9415                    self.write_newline();
9416                }
9417                self.write_indent();
9418                self.generate_alter_action_with_continuation(action, is_continuation)?;
9419            }
9420            self.indent_level -= 1;
9421        } else {
9422            for (i, action) in at.actions.iter().enumerate() {
9423                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
9424                let is_continuation = i > 0
9425                    && matches!(
9426                        (&at.actions[i - 1], action),
9427                        (
9428                            AlterTableAction::AddColumn { .. },
9429                            AlterTableAction::AddColumn { .. }
9430                        ) | (
9431                            AlterTableAction::AddConstraint(_),
9432                            AlterTableAction::AddConstraint(_)
9433                        )
9434                    );
9435                if i > 0 {
9436                    self.write(",");
9437                }
9438                self.write_space();
9439                self.generate_alter_action_with_continuation(action, is_continuation)?;
9440            }
9441        }
9442
9443        // MySQL ALTER TABLE trailing options
9444        if let Some(ref algorithm) = at.algorithm {
9445            self.write(", ");
9446            self.write_keyword("ALGORITHM");
9447            self.write("=");
9448            self.write_keyword(algorithm);
9449        }
9450        if let Some(ref lock) = at.lock {
9451            self.write(", ");
9452            self.write_keyword("LOCK");
9453            self.write("=");
9454            self.write_keyword(lock);
9455        }
9456
9457        // Restore Athena Hive context
9458        self.athena_hive_context = saved_athena_hive_context;
9459
9460        Ok(())
9461    }
9462
9463    fn generate_alter_action_with_continuation(
9464        &mut self,
9465        action: &AlterTableAction,
9466        is_continuation: bool,
9467    ) -> Result<()> {
9468        match action {
9469            AlterTableAction::AddColumn {
9470                column,
9471                if_not_exists,
9472                position,
9473            } => {
9474                use crate::dialects::DialectType;
9475                // For Snowflake: consecutive ADD COLUMN actions are combined with commas
9476                // e.g., "ADD col1, col2" instead of "ADD col1, ADD col2"
9477                // For other dialects, repeat ADD COLUMN for each
9478                let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
9479                let is_tsql_like = matches!(
9480                    self.config.dialect,
9481                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
9482                );
9483                // Athena uses "ADD COLUMNS (col_def)" instead of "ADD COLUMN col_def"
9484                let is_athena = matches!(self.config.dialect, Some(DialectType::Athena));
9485
9486                if is_continuation && (is_snowflake || is_tsql_like) {
9487                    // Don't write ADD keyword for continuation in Snowflake/TSQL
9488                } else if is_snowflake {
9489                    self.write_keyword("ADD");
9490                    self.write_space();
9491                } else if is_athena {
9492                    // Athena uses ADD COLUMNS (col_def) syntax
9493                    self.write_keyword("ADD COLUMNS");
9494                    self.write(" (");
9495                } else if self.config.alter_table_include_column_keyword {
9496                    self.write_keyword("ADD COLUMN");
9497                    self.write_space();
9498                } else {
9499                    // Dialects like Oracle and TSQL don't use COLUMN keyword
9500                    self.write_keyword("ADD");
9501                    self.write_space();
9502                }
9503
9504                if *if_not_exists {
9505                    self.write_keyword("IF NOT EXISTS");
9506                    self.write_space();
9507                }
9508                self.generate_column_def(column)?;
9509
9510                // Close parenthesis for Athena
9511                if is_athena {
9512                    self.write(")");
9513                }
9514
9515                // Column position (FIRST or AFTER)
9516                if let Some(pos) = position {
9517                    self.write_space();
9518                    match pos {
9519                        ColumnPosition::First => self.write_keyword("FIRST"),
9520                        ColumnPosition::After(col_name) => {
9521                            self.write_keyword("AFTER");
9522                            self.write_space();
9523                            self.generate_identifier(col_name)?;
9524                        }
9525                    }
9526                }
9527            }
9528            AlterTableAction::DropColumn {
9529                name,
9530                if_exists,
9531                cascade,
9532            } => {
9533                self.write_keyword("DROP COLUMN");
9534                if *if_exists {
9535                    self.write_space();
9536                    self.write_keyword("IF EXISTS");
9537                }
9538                self.write_space();
9539                self.generate_identifier(name)?;
9540                if *cascade {
9541                    self.write_space();
9542                    self.write_keyword("CASCADE");
9543                }
9544            }
9545            AlterTableAction::DropColumns { names } => {
9546                self.write_keyword("DROP COLUMNS");
9547                self.write(" (");
9548                for (i, name) in names.iter().enumerate() {
9549                    if i > 0 {
9550                        self.write(", ");
9551                    }
9552                    self.generate_identifier(name)?;
9553                }
9554                self.write(")");
9555            }
9556            AlterTableAction::RenameColumn {
9557                old_name,
9558                new_name,
9559                if_exists,
9560            } => {
9561                self.write_keyword("RENAME COLUMN");
9562                if *if_exists {
9563                    self.write_space();
9564                    self.write_keyword("IF EXISTS");
9565                }
9566                self.write_space();
9567                self.generate_identifier(old_name)?;
9568                self.write_space();
9569                self.write_keyword("TO");
9570                self.write_space();
9571                self.generate_identifier(new_name)?;
9572            }
9573            AlterTableAction::AlterColumn {
9574                name,
9575                action,
9576                use_modify_keyword,
9577            } => {
9578                use crate::dialects::DialectType;
9579                // MySQL uses MODIFY COLUMN for type changes (SetDataType)
9580                // but ALTER COLUMN for SET DEFAULT, DROP DEFAULT, etc.
9581                let use_modify = *use_modify_keyword
9582                    || (matches!(self.config.dialect, Some(DialectType::MySQL))
9583                        && matches!(action, AlterColumnAction::SetDataType { .. }));
9584                if use_modify {
9585                    self.write_keyword("MODIFY COLUMN");
9586                    self.write_space();
9587                    self.generate_identifier(name)?;
9588                    // For MODIFY COLUMN, output the type directly
9589                    if let AlterColumnAction::SetDataType {
9590                        data_type,
9591                        using: _,
9592                        collate,
9593                    } = action
9594                    {
9595                        self.write_space();
9596                        self.generate_data_type(data_type)?;
9597                        // Output COLLATE clause if present
9598                        if let Some(collate_name) = collate {
9599                            self.write_space();
9600                            self.write_keyword("COLLATE");
9601                            self.write_space();
9602                            // Output as single-quoted string
9603                            self.write(&format!("'{}'", collate_name));
9604                        }
9605                    } else {
9606                        self.write_space();
9607                        self.generate_alter_column_action(action)?;
9608                    }
9609                } else if matches!(self.config.dialect, Some(DialectType::Hive))
9610                    && matches!(action, AlterColumnAction::SetDataType { .. })
9611                {
9612                    // Hive uses CHANGE COLUMN col_name col_name NEW_TYPE
9613                    self.write_keyword("CHANGE COLUMN");
9614                    self.write_space();
9615                    self.generate_identifier(name)?;
9616                    self.write_space();
9617                    self.generate_identifier(name)?;
9618                    if let AlterColumnAction::SetDataType { data_type, .. } = action {
9619                        self.write_space();
9620                        self.generate_data_type(data_type)?;
9621                    }
9622                } else {
9623                    self.write_keyword("ALTER COLUMN");
9624                    self.write_space();
9625                    self.generate_identifier(name)?;
9626                    self.write_space();
9627                    self.generate_alter_column_action(action)?;
9628                }
9629            }
9630            AlterTableAction::RenameTable(new_name) => {
9631                // MySQL-like dialects (MySQL, Doris, StarRocks) use RENAME without TO
9632                let mysql_like = matches!(
9633                    self.config.dialect,
9634                    Some(DialectType::MySQL)
9635                        | Some(DialectType::Doris)
9636                        | Some(DialectType::StarRocks)
9637                        | Some(DialectType::SingleStore)
9638                );
9639                if mysql_like {
9640                    self.write_keyword("RENAME");
9641                } else {
9642                    self.write_keyword("RENAME TO");
9643                }
9644                self.write_space();
9645                // Doris, DuckDB, BigQuery, PostgreSQL strip schema/catalog from target table
9646                let rename_table_with_db = !matches!(
9647                    self.config.dialect,
9648                    Some(DialectType::Doris)
9649                        | Some(DialectType::DuckDB)
9650                        | Some(DialectType::BigQuery)
9651                        | Some(DialectType::PostgreSQL)
9652                );
9653                if !rename_table_with_db {
9654                    let mut stripped = new_name.clone();
9655                    stripped.schema = None;
9656                    stripped.catalog = None;
9657                    self.generate_table(&stripped)?;
9658                } else {
9659                    self.generate_table(new_name)?;
9660                }
9661            }
9662            AlterTableAction::AddConstraint(constraint) => {
9663                // For consecutive ADD CONSTRAINT actions (is_continuation=true), skip ADD keyword
9664                // to produce: ADD CONSTRAINT c1 ..., CONSTRAINT c2 ...
9665                if !is_continuation {
9666                    self.write_keyword("ADD");
9667                    self.write_space();
9668                }
9669                self.generate_table_constraint(constraint)?;
9670            }
9671            AlterTableAction::DropConstraint { name, if_exists } => {
9672                self.write_keyword("DROP CONSTRAINT");
9673                if *if_exists {
9674                    self.write_space();
9675                    self.write_keyword("IF EXISTS");
9676                }
9677                self.write_space();
9678                self.generate_identifier(name)?;
9679            }
9680            AlterTableAction::DropForeignKey { name } => {
9681                self.write_keyword("DROP FOREIGN KEY");
9682                self.write_space();
9683                self.generate_identifier(name)?;
9684            }
9685            AlterTableAction::DropPartition {
9686                partitions,
9687                if_exists,
9688            } => {
9689                self.write_keyword("DROP");
9690                if *if_exists {
9691                    self.write_space();
9692                    self.write_keyword("IF EXISTS");
9693                }
9694                for (i, partition) in partitions.iter().enumerate() {
9695                    if i > 0 {
9696                        self.write(",");
9697                    }
9698                    self.write_space();
9699                    self.write_keyword("PARTITION");
9700                    // Check for special ClickHouse partition formats
9701                    if partition.len() == 1 && partition[0].0.name == "__expr__" {
9702                        // ClickHouse: PARTITION <expression>
9703                        self.write_space();
9704                        self.generate_expression(&partition[0].1)?;
9705                    } else if partition.len() == 1 && partition[0].0.name == "ALL" {
9706                        // ClickHouse: PARTITION ALL
9707                        self.write_space();
9708                        self.write_keyword("ALL");
9709                    } else if partition.len() == 1 && partition[0].0.name == "ID" {
9710                        // ClickHouse: PARTITION ID 'string'
9711                        self.write_space();
9712                        self.write_keyword("ID");
9713                        self.write_space();
9714                        self.generate_expression(&partition[0].1)?;
9715                    } else {
9716                        // Standard SQL: PARTITION(key=value, ...)
9717                        self.write("(");
9718                        for (j, (key, value)) in partition.iter().enumerate() {
9719                            if j > 0 {
9720                                self.write(", ");
9721                            }
9722                            self.generate_identifier(key)?;
9723                            self.write(" = ");
9724                            self.generate_expression(value)?;
9725                        }
9726                        self.write(")");
9727                    }
9728                }
9729            }
9730            AlterTableAction::Delete { where_clause } => {
9731                self.write_keyword("DELETE");
9732                self.write_space();
9733                self.write_keyword("WHERE");
9734                self.write_space();
9735                self.generate_expression(where_clause)?;
9736            }
9737            AlterTableAction::SwapWith(target) => {
9738                self.write_keyword("SWAP WITH");
9739                self.write_space();
9740                self.generate_table(target)?;
9741            }
9742            AlterTableAction::SetProperty { properties } => {
9743                use crate::dialects::DialectType;
9744                self.write_keyword("SET");
9745                // Trino/Presto use SET PROPERTIES syntax with spaces around =
9746                let is_trino_presto = matches!(
9747                    self.config.dialect,
9748                    Some(DialectType::Trino) | Some(DialectType::Presto)
9749                );
9750                if is_trino_presto {
9751                    self.write_space();
9752                    self.write_keyword("PROPERTIES");
9753                }
9754                let eq = if is_trino_presto { " = " } else { "=" };
9755                for (i, (key, value)) in properties.iter().enumerate() {
9756                    if i > 0 {
9757                        self.write(",");
9758                    }
9759                    self.write_space();
9760                    // Handle quoted property names for Trino
9761                    if key.contains(' ') {
9762                        self.generate_string_literal(key)?;
9763                    } else {
9764                        self.write(key);
9765                    }
9766                    self.write(eq);
9767                    self.generate_expression(value)?;
9768                }
9769            }
9770            AlterTableAction::UnsetProperty { properties } => {
9771                self.write_keyword("UNSET");
9772                for (i, name) in properties.iter().enumerate() {
9773                    if i > 0 {
9774                        self.write(",");
9775                    }
9776                    self.write_space();
9777                    self.write(name);
9778                }
9779            }
9780            AlterTableAction::ClusterBy { expressions } => {
9781                self.write_keyword("CLUSTER BY");
9782                self.write(" (");
9783                for (i, expr) in expressions.iter().enumerate() {
9784                    if i > 0 {
9785                        self.write(", ");
9786                    }
9787                    self.generate_expression(expr)?;
9788                }
9789                self.write(")");
9790            }
9791            AlterTableAction::SetTag { expressions } => {
9792                self.write_keyword("SET TAG");
9793                for (i, (key, value)) in expressions.iter().enumerate() {
9794                    if i > 0 {
9795                        self.write(",");
9796                    }
9797                    self.write_space();
9798                    self.write(key);
9799                    self.write(" = ");
9800                    self.generate_expression(value)?;
9801                }
9802            }
9803            AlterTableAction::UnsetTag { names } => {
9804                self.write_keyword("UNSET TAG");
9805                for (i, name) in names.iter().enumerate() {
9806                    if i > 0 {
9807                        self.write(",");
9808                    }
9809                    self.write_space();
9810                    self.write(name);
9811                }
9812            }
9813            AlterTableAction::SetOptions { expressions } => {
9814                self.write_keyword("SET");
9815                self.write(" (");
9816                for (i, expr) in expressions.iter().enumerate() {
9817                    if i > 0 {
9818                        self.write(", ");
9819                    }
9820                    self.generate_expression(expr)?;
9821                }
9822                self.write(")");
9823            }
9824            AlterTableAction::AlterIndex { name, visible } => {
9825                self.write_keyword("ALTER INDEX");
9826                self.write_space();
9827                self.generate_identifier(name)?;
9828                self.write_space();
9829                if *visible {
9830                    self.write_keyword("VISIBLE");
9831                } else {
9832                    self.write_keyword("INVISIBLE");
9833                }
9834            }
9835            AlterTableAction::SetAttribute { attribute } => {
9836                self.write_keyword("SET");
9837                self.write_space();
9838                self.write_keyword(attribute);
9839            }
9840            AlterTableAction::SetStageFileFormat { options } => {
9841                self.write_keyword("SET");
9842                self.write_space();
9843                self.write_keyword("STAGE_FILE_FORMAT");
9844                self.write(" = (");
9845                if let Some(opts) = options {
9846                    self.generate_space_separated_properties(opts)?;
9847                }
9848                self.write(")");
9849            }
9850            AlterTableAction::SetStageCopyOptions { options } => {
9851                self.write_keyword("SET");
9852                self.write_space();
9853                self.write_keyword("STAGE_COPY_OPTIONS");
9854                self.write(" = (");
9855                if let Some(opts) = options {
9856                    self.generate_space_separated_properties(opts)?;
9857                }
9858                self.write(")");
9859            }
9860            AlterTableAction::AddColumns { columns, cascade } => {
9861                // Oracle uses ADD (...) without COLUMNS keyword
9862                // Hive/Spark uses ADD COLUMNS (...)
9863                let is_oracle = matches!(self.config.dialect, Some(DialectType::Oracle));
9864                if is_oracle {
9865                    self.write_keyword("ADD");
9866                } else {
9867                    self.write_keyword("ADD COLUMNS");
9868                }
9869                self.write(" (");
9870                for (i, col) in columns.iter().enumerate() {
9871                    if i > 0 {
9872                        self.write(", ");
9873                    }
9874                    self.generate_column_def(col)?;
9875                }
9876                self.write(")");
9877                if *cascade {
9878                    self.write_space();
9879                    self.write_keyword("CASCADE");
9880                }
9881            }
9882            AlterTableAction::ChangeColumn {
9883                old_name,
9884                new_name,
9885                data_type,
9886                comment,
9887                cascade,
9888            } => {
9889                use crate::dialects::DialectType;
9890                let is_spark = matches!(
9891                    self.config.dialect,
9892                    Some(DialectType::Spark) | Some(DialectType::Databricks)
9893                );
9894                let is_rename = old_name.name != new_name.name;
9895
9896                if is_spark {
9897                    if is_rename {
9898                        // Spark: RENAME COLUMN old TO new
9899                        self.write_keyword("RENAME COLUMN");
9900                        self.write_space();
9901                        self.generate_identifier(old_name)?;
9902                        self.write_space();
9903                        self.write_keyword("TO");
9904                        self.write_space();
9905                        self.generate_identifier(new_name)?;
9906                    } else if comment.is_some() {
9907                        // Spark: ALTER COLUMN old COMMENT 'comment'
9908                        self.write_keyword("ALTER COLUMN");
9909                        self.write_space();
9910                        self.generate_identifier(old_name)?;
9911                        self.write_space();
9912                        self.write_keyword("COMMENT");
9913                        self.write_space();
9914                        self.write("'");
9915                        self.write(comment.as_ref().unwrap());
9916                        self.write("'");
9917                    } else if data_type.is_some() {
9918                        // Spark: ALTER COLUMN old TYPE data_type
9919                        self.write_keyword("ALTER COLUMN");
9920                        self.write_space();
9921                        self.generate_identifier(old_name)?;
9922                        self.write_space();
9923                        self.write_keyword("TYPE");
9924                        self.write_space();
9925                        self.generate_data_type(data_type.as_ref().unwrap())?;
9926                    } else {
9927                        // Fallback to CHANGE COLUMN
9928                        self.write_keyword("CHANGE COLUMN");
9929                        self.write_space();
9930                        self.generate_identifier(old_name)?;
9931                        self.write_space();
9932                        self.generate_identifier(new_name)?;
9933                    }
9934                } else {
9935                    // Hive/MySQL/default: CHANGE [COLUMN] old new [type] [COMMENT '...'] [CASCADE]
9936                    if data_type.is_some() {
9937                        self.write_keyword("CHANGE COLUMN");
9938                    } else {
9939                        self.write_keyword("CHANGE");
9940                    }
9941                    self.write_space();
9942                    self.generate_identifier(old_name)?;
9943                    self.write_space();
9944                    self.generate_identifier(new_name)?;
9945                    if let Some(ref dt) = data_type {
9946                        self.write_space();
9947                        self.generate_data_type(dt)?;
9948                    }
9949                    if let Some(ref c) = comment {
9950                        self.write_space();
9951                        self.write_keyword("COMMENT");
9952                        self.write_space();
9953                        self.write("'");
9954                        self.write(c);
9955                        self.write("'");
9956                    }
9957                    if *cascade {
9958                        self.write_space();
9959                        self.write_keyword("CASCADE");
9960                    }
9961                }
9962            }
9963            AlterTableAction::AddPartition {
9964                partition,
9965                if_not_exists,
9966                location,
9967            } => {
9968                self.write_keyword("ADD");
9969                self.write_space();
9970                if *if_not_exists {
9971                    self.write_keyword("IF NOT EXISTS");
9972                    self.write_space();
9973                }
9974                self.generate_expression(partition)?;
9975                if let Some(ref loc) = location {
9976                    self.write_space();
9977                    self.write_keyword("LOCATION");
9978                    self.write_space();
9979                    self.generate_expression(loc)?;
9980                }
9981            }
9982            AlterTableAction::AlterSortKey {
9983                this,
9984                expressions,
9985                compound,
9986            } => {
9987                // Redshift: ALTER [COMPOUND] SORTKEY AUTO|NONE|(col1, col2)
9988                self.write_keyword("ALTER");
9989                if *compound {
9990                    self.write_space();
9991                    self.write_keyword("COMPOUND");
9992                }
9993                self.write_space();
9994                self.write_keyword("SORTKEY");
9995                self.write_space();
9996                if let Some(style) = this {
9997                    self.write_keyword(style);
9998                } else if !expressions.is_empty() {
9999                    self.write("(");
10000                    for (i, expr) in expressions.iter().enumerate() {
10001                        if i > 0 {
10002                            self.write(", ");
10003                        }
10004                        self.generate_expression(expr)?;
10005                    }
10006                    self.write(")");
10007                }
10008            }
10009            AlterTableAction::AlterDistStyle { style, distkey } => {
10010                // Redshift: ALTER DISTSTYLE ALL|EVEN|AUTO|KEY [DISTKEY col]
10011                self.write_keyword("ALTER");
10012                self.write_space();
10013                self.write_keyword("DISTSTYLE");
10014                self.write_space();
10015                self.write_keyword(style);
10016                if let Some(col) = distkey {
10017                    self.write_space();
10018                    self.write_keyword("DISTKEY");
10019                    self.write_space();
10020                    self.generate_identifier(col)?;
10021                }
10022            }
10023            AlterTableAction::SetTableProperties { properties } => {
10024                // Redshift: SET TABLE PROPERTIES ('a' = '5', 'b' = 'c')
10025                self.write_keyword("SET TABLE PROPERTIES");
10026                self.write(" (");
10027                for (i, (key, value)) in properties.iter().enumerate() {
10028                    if i > 0 {
10029                        self.write(", ");
10030                    }
10031                    self.generate_expression(key)?;
10032                    self.write(" = ");
10033                    self.generate_expression(value)?;
10034                }
10035                self.write(")");
10036            }
10037            AlterTableAction::SetLocation { location } => {
10038                // Redshift: SET LOCATION 's3://bucket/folder/'
10039                self.write_keyword("SET LOCATION");
10040                self.write_space();
10041                self.write("'");
10042                self.write(location);
10043                self.write("'");
10044            }
10045            AlterTableAction::SetFileFormat { format } => {
10046                // Redshift: SET FILE FORMAT AVRO
10047                self.write_keyword("SET FILE FORMAT");
10048                self.write_space();
10049                self.write_keyword(format);
10050            }
10051            AlterTableAction::ReplacePartition { partition, source } => {
10052                // ClickHouse: REPLACE PARTITION expr FROM source
10053                self.write_keyword("REPLACE PARTITION");
10054                self.write_space();
10055                self.generate_expression(partition)?;
10056                if let Some(src) = source {
10057                    self.write_space();
10058                    self.write_keyword("FROM");
10059                    self.write_space();
10060                    self.generate_expression(src)?;
10061                }
10062            }
10063            AlterTableAction::Raw { sql } => {
10064                self.write(sql);
10065            }
10066        }
10067        Ok(())
10068    }
10069
10070    fn generate_alter_column_action(&mut self, action: &AlterColumnAction) -> Result<()> {
10071        match action {
10072            AlterColumnAction::SetDataType {
10073                data_type,
10074                using,
10075                collate,
10076            } => {
10077                use crate::dialects::DialectType;
10078                // Dialect-specific type change syntax:
10079                // - TSQL/Fabric/Hive: no prefix (ALTER COLUMN col datatype)
10080                // - Redshift/Spark: TYPE (ALTER COLUMN col TYPE datatype)
10081                // - Default: SET DATA TYPE (ALTER COLUMN col SET DATA TYPE datatype)
10082                let is_no_prefix = matches!(
10083                    self.config.dialect,
10084                    Some(DialectType::TSQL) | Some(DialectType::Fabric) | Some(DialectType::Hive)
10085                );
10086                let is_type_only = matches!(
10087                    self.config.dialect,
10088                    Some(DialectType::Redshift)
10089                        | Some(DialectType::Spark)
10090                        | Some(DialectType::Databricks)
10091                );
10092                if is_type_only {
10093                    self.write_keyword("TYPE");
10094                    self.write_space();
10095                } else if !is_no_prefix {
10096                    self.write_keyword("SET DATA TYPE");
10097                    self.write_space();
10098                }
10099                self.generate_data_type(data_type)?;
10100                if let Some(ref collation) = collate {
10101                    self.write_space();
10102                    self.write_keyword("COLLATE");
10103                    self.write_space();
10104                    self.write(collation);
10105                }
10106                if let Some(ref using_expr) = using {
10107                    self.write_space();
10108                    self.write_keyword("USING");
10109                    self.write_space();
10110                    self.generate_expression(using_expr)?;
10111                }
10112            }
10113            AlterColumnAction::SetDefault(expr) => {
10114                self.write_keyword("SET DEFAULT");
10115                self.write_space();
10116                self.generate_expression(expr)?;
10117            }
10118            AlterColumnAction::DropDefault => {
10119                self.write_keyword("DROP DEFAULT");
10120            }
10121            AlterColumnAction::SetNotNull => {
10122                self.write_keyword("SET NOT NULL");
10123            }
10124            AlterColumnAction::DropNotNull => {
10125                self.write_keyword("DROP NOT NULL");
10126            }
10127            AlterColumnAction::Comment(comment) => {
10128                self.write_keyword("COMMENT");
10129                self.write_space();
10130                self.generate_string_literal(comment)?;
10131            }
10132            AlterColumnAction::SetVisible => {
10133                self.write_keyword("SET VISIBLE");
10134            }
10135            AlterColumnAction::SetInvisible => {
10136                self.write_keyword("SET INVISIBLE");
10137            }
10138        }
10139        Ok(())
10140    }
10141
10142    fn generate_create_index(&mut self, ci: &CreateIndex) -> Result<()> {
10143        self.write_keyword("CREATE");
10144
10145        if ci.unique {
10146            self.write_space();
10147            self.write_keyword("UNIQUE");
10148        }
10149
10150        // TSQL CLUSTERED/NONCLUSTERED modifier
10151        if let Some(ref clustered) = ci.clustered {
10152            self.write_space();
10153            self.write_keyword(clustered);
10154        }
10155
10156        self.write_space();
10157        self.write_keyword("INDEX");
10158
10159        // PostgreSQL CONCURRENTLY modifier
10160        if ci.concurrently {
10161            self.write_space();
10162            self.write_keyword("CONCURRENTLY");
10163        }
10164
10165        if ci.if_not_exists {
10166            self.write_space();
10167            self.write_keyword("IF NOT EXISTS");
10168        }
10169
10170        // Index name is optional in PostgreSQL when IF NOT EXISTS is specified
10171        if !ci.name.name.is_empty() {
10172            self.write_space();
10173            self.generate_identifier(&ci.name)?;
10174        }
10175        self.write_space();
10176        self.write_keyword("ON");
10177        // Hive uses ON TABLE
10178        if matches!(self.config.dialect, Some(DialectType::Hive)) {
10179            self.write_space();
10180            self.write_keyword("TABLE");
10181        }
10182        self.write_space();
10183        self.generate_table(&ci.table)?;
10184
10185        // Column list (optional for COLUMNSTORE indexes)
10186        // Standard SQL convention: ON t(a) without space before paren
10187        if !ci.columns.is_empty() || ci.using.is_some() {
10188            let space_before_paren = false;
10189
10190            if let Some(ref using) = ci.using {
10191                self.write_space();
10192                self.write_keyword("USING");
10193                self.write_space();
10194                self.write(using);
10195                if space_before_paren {
10196                    self.write(" (");
10197                } else {
10198                    self.write("(");
10199                }
10200            } else {
10201                if space_before_paren {
10202                    self.write(" (");
10203                } else {
10204                    self.write("(");
10205                }
10206            }
10207            for (i, col) in ci.columns.iter().enumerate() {
10208                if i > 0 {
10209                    self.write(", ");
10210                }
10211                self.generate_identifier(&col.column)?;
10212                if let Some(ref opclass) = col.opclass {
10213                    self.write_space();
10214                    self.write(opclass);
10215                }
10216                if col.desc {
10217                    self.write_space();
10218                    self.write_keyword("DESC");
10219                } else if col.asc {
10220                    self.write_space();
10221                    self.write_keyword("ASC");
10222                }
10223                if let Some(nulls_first) = col.nulls_first {
10224                    self.write_space();
10225                    self.write_keyword("NULLS");
10226                    self.write_space();
10227                    self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
10228                }
10229            }
10230            self.write(")");
10231        }
10232
10233        // PostgreSQL INCLUDE (col1, col2) clause
10234        if !ci.include_columns.is_empty() {
10235            self.write_space();
10236            self.write_keyword("INCLUDE");
10237            self.write(" (");
10238            for (i, col) in ci.include_columns.iter().enumerate() {
10239                if i > 0 {
10240                    self.write(", ");
10241                }
10242                self.generate_identifier(col)?;
10243            }
10244            self.write(")");
10245        }
10246
10247        // TSQL: WITH (option=value, ...) clause
10248        if !ci.with_options.is_empty() {
10249            self.write_space();
10250            self.write_keyword("WITH");
10251            self.write(" (");
10252            for (i, (key, value)) in ci.with_options.iter().enumerate() {
10253                if i > 0 {
10254                    self.write(", ");
10255                }
10256                self.write(key);
10257                self.write("=");
10258                self.write(value);
10259            }
10260            self.write(")");
10261        }
10262
10263        // PostgreSQL WHERE clause for partial indexes
10264        if let Some(ref where_clause) = ci.where_clause {
10265            self.write_space();
10266            self.write_keyword("WHERE");
10267            self.write_space();
10268            self.generate_expression(where_clause)?;
10269        }
10270
10271        // TSQL: ON filegroup or partition scheme clause
10272        if let Some(ref on_fg) = ci.on_filegroup {
10273            self.write_space();
10274            self.write_keyword("ON");
10275            self.write_space();
10276            self.write(on_fg);
10277        }
10278
10279        Ok(())
10280    }
10281
10282    fn generate_drop_index(&mut self, di: &DropIndex) -> Result<()> {
10283        self.write_keyword("DROP INDEX");
10284
10285        if di.concurrently {
10286            self.write_space();
10287            self.write_keyword("CONCURRENTLY");
10288        }
10289
10290        if di.if_exists {
10291            self.write_space();
10292            self.write_keyword("IF EXISTS");
10293        }
10294
10295        self.write_space();
10296        self.generate_identifier(&di.name)?;
10297
10298        if let Some(ref table) = di.table {
10299            self.write_space();
10300            self.write_keyword("ON");
10301            self.write_space();
10302            self.generate_table(table)?;
10303        }
10304
10305        Ok(())
10306    }
10307
10308    fn generate_create_view(&mut self, cv: &CreateView) -> Result<()> {
10309        self.write_keyword("CREATE");
10310
10311        // MySQL: ALGORITHM=...
10312        if let Some(ref algorithm) = cv.algorithm {
10313            self.write_space();
10314            self.write_keyword("ALGORITHM");
10315            self.write("=");
10316            self.write_keyword(algorithm);
10317        }
10318
10319        // MySQL: DEFINER=...
10320        if let Some(ref definer) = cv.definer {
10321            self.write_space();
10322            self.write_keyword("DEFINER");
10323            self.write("=");
10324            self.write(definer);
10325        }
10326
10327        // MySQL: SQL SECURITY DEFINER/INVOKER (before VIEW keyword)
10328        if cv.security_sql_style {
10329            if let Some(ref security) = cv.security {
10330                self.write_space();
10331                self.write_keyword("SQL SECURITY");
10332                self.write_space();
10333                match security {
10334                    FunctionSecurity::Definer => self.write_keyword("DEFINER"),
10335                    FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
10336                    FunctionSecurity::None => self.write_keyword("NONE"),
10337                }
10338            }
10339        }
10340
10341        if cv.or_replace {
10342            self.write_space();
10343            self.write_keyword("OR REPLACE");
10344        }
10345
10346        if cv.temporary {
10347            self.write_space();
10348            self.write_keyword("TEMPORARY");
10349        }
10350
10351        if cv.materialized {
10352            self.write_space();
10353            self.write_keyword("MATERIALIZED");
10354        }
10355
10356        // Snowflake: SECURE VIEW
10357        if cv.secure {
10358            self.write_space();
10359            self.write_keyword("SECURE");
10360        }
10361
10362        self.write_space();
10363        self.write_keyword("VIEW");
10364
10365        if cv.if_not_exists {
10366            self.write_space();
10367            self.write_keyword("IF NOT EXISTS");
10368        }
10369
10370        self.write_space();
10371        self.generate_table(&cv.name)?;
10372
10373        // ClickHouse: ON CLUSTER clause
10374        if let Some(ref on_cluster) = cv.on_cluster {
10375            self.write_space();
10376            self.generate_on_cluster(on_cluster)?;
10377        }
10378
10379        // ClickHouse: TO destination_table
10380        if let Some(ref to_table) = cv.to_table {
10381            self.write_space();
10382            self.write_keyword("TO");
10383            self.write_space();
10384            self.generate_table(to_table)?;
10385        }
10386
10387        // For regular VIEW: columns come before COPY GRANTS
10388        // For MATERIALIZED VIEW: COPY GRANTS comes before columns
10389        if !cv.materialized {
10390            // Regular VIEW: columns first
10391            if !cv.columns.is_empty() {
10392                self.write(" (");
10393                for (i, col) in cv.columns.iter().enumerate() {
10394                    if i > 0 {
10395                        self.write(", ");
10396                    }
10397                    self.generate_identifier(&col.name)?;
10398                    // BigQuery: OPTIONS (key=value, ...) on view column
10399                    if !col.options.is_empty() {
10400                        self.write_space();
10401                        self.generate_options_clause(&col.options)?;
10402                    }
10403                    if let Some(ref comment) = col.comment {
10404                        self.write_space();
10405                        self.write_keyword("COMMENT");
10406                        self.write_space();
10407                        self.generate_string_literal(comment)?;
10408                    }
10409                }
10410                self.write(")");
10411            }
10412
10413            // Presto/Trino/StarRocks: SECURITY DEFINER/INVOKER/NONE (after columns)
10414            if !cv.security_sql_style {
10415                if let Some(ref security) = cv.security {
10416                    self.write_space();
10417                    self.write_keyword("SECURITY");
10418                    self.write_space();
10419                    match security {
10420                        FunctionSecurity::Definer => self.write_keyword("DEFINER"),
10421                        FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
10422                        FunctionSecurity::None => self.write_keyword("NONE"),
10423                    }
10424                }
10425            }
10426
10427            // Snowflake: COPY GRANTS
10428            if cv.copy_grants {
10429                self.write_space();
10430                self.write_keyword("COPY GRANTS");
10431            }
10432        } else {
10433            // MATERIALIZED VIEW: COPY GRANTS first
10434            if cv.copy_grants {
10435                self.write_space();
10436                self.write_keyword("COPY GRANTS");
10437            }
10438
10439            // Doris: If we have a schema (typed columns), generate that instead
10440            if let Some(ref schema) = cv.schema {
10441                self.write(" (");
10442                for (i, expr) in schema.expressions.iter().enumerate() {
10443                    if i > 0 {
10444                        self.write(", ");
10445                    }
10446                    self.generate_expression(expr)?;
10447                }
10448                self.write(")");
10449            } else if !cv.columns.is_empty() {
10450                // Then columns (simple column names without types)
10451                self.write(" (");
10452                for (i, col) in cv.columns.iter().enumerate() {
10453                    if i > 0 {
10454                        self.write(", ");
10455                    }
10456                    self.generate_identifier(&col.name)?;
10457                    // BigQuery: OPTIONS (key=value, ...) on view column
10458                    if !col.options.is_empty() {
10459                        self.write_space();
10460                        self.generate_options_clause(&col.options)?;
10461                    }
10462                    if let Some(ref comment) = col.comment {
10463                        self.write_space();
10464                        self.write_keyword("COMMENT");
10465                        self.write_space();
10466                        self.generate_string_literal(comment)?;
10467                    }
10468                }
10469                self.write(")");
10470            }
10471
10472            // Doris: KEY (columns) for materialized views
10473            if let Some(ref unique_key) = cv.unique_key {
10474                self.write_space();
10475                self.write_keyword("KEY");
10476                self.write(" (");
10477                for (i, expr) in unique_key.expressions.iter().enumerate() {
10478                    if i > 0 {
10479                        self.write(", ");
10480                    }
10481                    self.generate_expression(expr)?;
10482                }
10483                self.write(")");
10484            }
10485        }
10486
10487        // Snowflake: COMMENT = 'text'
10488        if let Some(ref comment) = cv.comment {
10489            self.write_space();
10490            self.write_keyword("COMMENT");
10491            self.write("=");
10492            self.generate_string_literal(comment)?;
10493        }
10494
10495        // Snowflake: TAG (name='value', ...)
10496        if !cv.tags.is_empty() {
10497            self.write_space();
10498            self.write_keyword("TAG");
10499            self.write(" (");
10500            for (i, (name, value)) in cv.tags.iter().enumerate() {
10501                if i > 0 {
10502                    self.write(", ");
10503                }
10504                self.write(name);
10505                self.write("='");
10506                self.write(value);
10507                self.write("'");
10508            }
10509            self.write(")");
10510        }
10511
10512        // BigQuery: OPTIONS (key=value, ...)
10513        if !cv.options.is_empty() {
10514            self.write_space();
10515            self.generate_options_clause(&cv.options)?;
10516        }
10517
10518        // Doris: BUILD IMMEDIATE/DEFERRED for materialized views
10519        if let Some(ref build) = cv.build {
10520            self.write_space();
10521            self.write_keyword("BUILD");
10522            self.write_space();
10523            self.write_keyword(build);
10524        }
10525
10526        // Doris: REFRESH clause for materialized views
10527        if let Some(ref refresh) = cv.refresh {
10528            self.write_space();
10529            self.generate_refresh_trigger_property(refresh)?;
10530        }
10531
10532        // Redshift: AUTO REFRESH YES|NO for materialized views
10533        if let Some(auto_refresh) = cv.auto_refresh {
10534            self.write_space();
10535            self.write_keyword("AUTO REFRESH");
10536            self.write_space();
10537            if auto_refresh {
10538                self.write_keyword("YES");
10539            } else {
10540                self.write_keyword("NO");
10541            }
10542        }
10543
10544        // ClickHouse: Table properties (ENGINE, ORDER BY, SAMPLE, SETTINGS, TTL, etc.)
10545        for prop in &cv.table_properties {
10546            self.write_space();
10547            self.generate_expression(prop)?;
10548        }
10549
10550        // Only output AS clause if there's a real query (not just NULL placeholder)
10551        if !matches!(&cv.query, Expression::Null(_)) {
10552            self.write_space();
10553            self.write_keyword("AS");
10554            self.write_space();
10555
10556            // Teradata: LOCKING clause (between AS and query)
10557            if let Some(ref mode) = cv.locking_mode {
10558                self.write_keyword("LOCKING");
10559                self.write_space();
10560                self.write_keyword(mode);
10561                if let Some(ref access) = cv.locking_access {
10562                    self.write_space();
10563                    self.write_keyword("FOR");
10564                    self.write_space();
10565                    self.write_keyword(access);
10566                }
10567                self.write_space();
10568            }
10569
10570            if cv.query_parenthesized {
10571                self.write("(");
10572            }
10573            self.generate_expression(&cv.query)?;
10574            if cv.query_parenthesized {
10575                self.write(")");
10576            }
10577        }
10578
10579        // Redshift: WITH NO SCHEMA BINDING (after query)
10580        if cv.no_schema_binding {
10581            self.write_space();
10582            self.write_keyword("WITH NO SCHEMA BINDING");
10583        }
10584
10585        Ok(())
10586    }
10587
10588    fn generate_drop_view(&mut self, dv: &DropView) -> Result<()> {
10589        self.write_keyword("DROP");
10590
10591        if dv.materialized {
10592            self.write_space();
10593            self.write_keyword("MATERIALIZED");
10594        }
10595
10596        self.write_space();
10597        self.write_keyword("VIEW");
10598
10599        if dv.if_exists {
10600            self.write_space();
10601            self.write_keyword("IF EXISTS");
10602        }
10603
10604        self.write_space();
10605        self.generate_table(&dv.name)?;
10606
10607        Ok(())
10608    }
10609
10610    fn generate_truncate(&mut self, tr: &Truncate) -> Result<()> {
10611        match tr.target {
10612            TruncateTarget::Database => self.write_keyword("TRUNCATE DATABASE"),
10613            TruncateTarget::Table => self.write_keyword("TRUNCATE TABLE"),
10614        }
10615        if tr.if_exists {
10616            self.write_space();
10617            self.write_keyword("IF EXISTS");
10618        }
10619        self.write_space();
10620        self.generate_table(&tr.table)?;
10621
10622        // ClickHouse: ON CLUSTER clause
10623        if let Some(ref on_cluster) = tr.on_cluster {
10624            self.write_space();
10625            self.generate_on_cluster(on_cluster)?;
10626        }
10627
10628        // Check if first table has a * (multi-table with star)
10629        if !tr.extra_tables.is_empty() {
10630            // Check if the first entry matches the main table (star case)
10631            let skip_first = if let Some(first) = tr.extra_tables.first() {
10632                first.table.name == tr.table.name && first.star
10633            } else {
10634                false
10635            };
10636
10637            // PostgreSQL normalizes away the * suffix (it's the default behavior)
10638            let strip_star = matches!(
10639                self.config.dialect,
10640                Some(crate::dialects::DialectType::PostgreSQL)
10641                    | Some(crate::dialects::DialectType::Redshift)
10642            );
10643            if skip_first && !strip_star {
10644                self.write("*");
10645            }
10646
10647            // Generate additional tables
10648            for (i, entry) in tr.extra_tables.iter().enumerate() {
10649                if i == 0 && skip_first {
10650                    continue; // Already handled the star for first table
10651                }
10652                self.write(", ");
10653                self.generate_table(&entry.table)?;
10654                if entry.star && !strip_star {
10655                    self.write("*");
10656                }
10657            }
10658        }
10659
10660        // RESTART/CONTINUE IDENTITY
10661        if let Some(identity) = &tr.identity {
10662            self.write_space();
10663            match identity {
10664                TruncateIdentity::Restart => self.write_keyword("RESTART IDENTITY"),
10665                TruncateIdentity::Continue => self.write_keyword("CONTINUE IDENTITY"),
10666            }
10667        }
10668
10669        if tr.cascade {
10670            self.write_space();
10671            self.write_keyword("CASCADE");
10672        }
10673
10674        if tr.restrict {
10675            self.write_space();
10676            self.write_keyword("RESTRICT");
10677        }
10678
10679        // Output Hive PARTITION clause
10680        if let Some(ref partition) = tr.partition {
10681            self.write_space();
10682            self.generate_expression(partition)?;
10683        }
10684
10685        Ok(())
10686    }
10687
10688    fn generate_use(&mut self, u: &Use) -> Result<()> {
10689        // Teradata uses "DATABASE <name>" instead of "USE <name>"
10690        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
10691            self.write_keyword("DATABASE");
10692            self.write_space();
10693            self.generate_identifier(&u.this)?;
10694            return Ok(());
10695        }
10696
10697        self.write_keyword("USE");
10698
10699        if let Some(kind) = &u.kind {
10700            self.write_space();
10701            match kind {
10702                UseKind::Database => self.write_keyword("DATABASE"),
10703                UseKind::Schema => self.write_keyword("SCHEMA"),
10704                UseKind::Role => self.write_keyword("ROLE"),
10705                UseKind::Warehouse => self.write_keyword("WAREHOUSE"),
10706                UseKind::Catalog => self.write_keyword("CATALOG"),
10707                UseKind::SecondaryRoles => self.write_keyword("SECONDARY ROLES"),
10708            }
10709        }
10710
10711        self.write_space();
10712        // For SECONDARY ROLES, write the value as-is (ALL, NONE, or role names)
10713        // without quoting, since these are keywords not identifiers
10714        if matches!(&u.kind, Some(UseKind::SecondaryRoles)) {
10715            self.write(&u.this.name);
10716        } else {
10717            self.generate_identifier(&u.this)?;
10718        }
10719        Ok(())
10720    }
10721
10722    fn generate_cache(&mut self, c: &Cache) -> Result<()> {
10723        self.write_keyword("CACHE");
10724        if c.lazy {
10725            self.write_space();
10726            self.write_keyword("LAZY");
10727        }
10728        self.write_space();
10729        self.write_keyword("TABLE");
10730        self.write_space();
10731        self.generate_identifier(&c.table)?;
10732
10733        // OPTIONS clause
10734        if !c.options.is_empty() {
10735            self.write_space();
10736            self.write_keyword("OPTIONS");
10737            self.write("(");
10738            for (i, (key, value)) in c.options.iter().enumerate() {
10739                if i > 0 {
10740                    self.write(", ");
10741                }
10742                self.generate_expression(key)?;
10743                self.write(" = ");
10744                self.generate_expression(value)?;
10745            }
10746            self.write(")");
10747        }
10748
10749        // AS query
10750        if let Some(query) = &c.query {
10751            self.write_space();
10752            self.write_keyword("AS");
10753            self.write_space();
10754            self.generate_expression(query)?;
10755        }
10756
10757        Ok(())
10758    }
10759
10760    fn generate_uncache(&mut self, u: &Uncache) -> Result<()> {
10761        self.write_keyword("UNCACHE TABLE");
10762        if u.if_exists {
10763            self.write_space();
10764            self.write_keyword("IF EXISTS");
10765        }
10766        self.write_space();
10767        self.generate_identifier(&u.table)?;
10768        Ok(())
10769    }
10770
10771    fn generate_load_data(&mut self, l: &LoadData) -> Result<()> {
10772        self.write_keyword("LOAD DATA");
10773        if l.local {
10774            self.write_space();
10775            self.write_keyword("LOCAL");
10776        }
10777        self.write_space();
10778        self.write_keyword("INPATH");
10779        self.write_space();
10780        self.write("'");
10781        self.write(&l.inpath);
10782        self.write("'");
10783
10784        if l.overwrite {
10785            self.write_space();
10786            self.write_keyword("OVERWRITE");
10787        }
10788
10789        self.write_space();
10790        self.write_keyword("INTO TABLE");
10791        self.write_space();
10792        self.generate_expression(&l.table)?;
10793
10794        // PARTITION clause
10795        if !l.partition.is_empty() {
10796            self.write_space();
10797            self.write_keyword("PARTITION");
10798            self.write("(");
10799            for (i, (col, val)) in l.partition.iter().enumerate() {
10800                if i > 0 {
10801                    self.write(", ");
10802                }
10803                self.generate_identifier(col)?;
10804                self.write(" = ");
10805                self.generate_expression(val)?;
10806            }
10807            self.write(")");
10808        }
10809
10810        // INPUTFORMAT clause
10811        if let Some(fmt) = &l.input_format {
10812            self.write_space();
10813            self.write_keyword("INPUTFORMAT");
10814            self.write_space();
10815            self.write("'");
10816            self.write(fmt);
10817            self.write("'");
10818        }
10819
10820        // SERDE clause
10821        if let Some(serde) = &l.serde {
10822            self.write_space();
10823            self.write_keyword("SERDE");
10824            self.write_space();
10825            self.write("'");
10826            self.write(serde);
10827            self.write("'");
10828        }
10829
10830        Ok(())
10831    }
10832
10833    fn generate_pragma(&mut self, p: &Pragma) -> Result<()> {
10834        self.write_keyword("PRAGMA");
10835        self.write_space();
10836
10837        // Schema prefix if present
10838        if let Some(schema) = &p.schema {
10839            self.generate_identifier(schema)?;
10840            self.write(".");
10841        }
10842
10843        // Pragma name
10844        self.generate_identifier(&p.name)?;
10845
10846        // Value assignment or function call
10847        if let Some(value) = &p.value {
10848            self.write(" = ");
10849            self.generate_expression(value)?;
10850        } else if !p.args.is_empty() {
10851            self.write("(");
10852            for (i, arg) in p.args.iter().enumerate() {
10853                if i > 0 {
10854                    self.write(", ");
10855                }
10856                self.generate_expression(arg)?;
10857            }
10858            self.write(")");
10859        }
10860
10861        Ok(())
10862    }
10863
10864    fn generate_grant(&mut self, g: &Grant) -> Result<()> {
10865        self.write_keyword("GRANT");
10866        self.write_space();
10867
10868        // Privileges (with optional column lists)
10869        for (i, privilege) in g.privileges.iter().enumerate() {
10870            if i > 0 {
10871                self.write(", ");
10872            }
10873            self.write_keyword(&privilege.name);
10874            // Output column list if present: SELECT(col1, col2)
10875            if !privilege.columns.is_empty() {
10876                self.write("(");
10877                for (j, col) in privilege.columns.iter().enumerate() {
10878                    if j > 0 {
10879                        self.write(", ");
10880                    }
10881                    self.write(col);
10882                }
10883                self.write(")");
10884            }
10885        }
10886
10887        self.write_space();
10888        self.write_keyword("ON");
10889        self.write_space();
10890
10891        // Object kind (TABLE, SCHEMA, etc.)
10892        if let Some(kind) = &g.kind {
10893            self.write_keyword(kind);
10894            self.write_space();
10895        }
10896
10897        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
10898        {
10899            use crate::dialects::DialectType;
10900            let should_upper = matches!(
10901                self.config.dialect,
10902                Some(DialectType::PostgreSQL)
10903                    | Some(DialectType::CockroachDB)
10904                    | Some(DialectType::Materialize)
10905                    | Some(DialectType::RisingWave)
10906            ) && (g.kind.as_deref() == Some("FUNCTION")
10907                || g.kind.as_deref() == Some("PROCEDURE"));
10908            if should_upper {
10909                use crate::expressions::Identifier;
10910                let upper_id = Identifier {
10911                    name: g.securable.name.to_uppercase(),
10912                    quoted: g.securable.quoted,
10913                    ..g.securable.clone()
10914                };
10915                self.generate_identifier(&upper_id)?;
10916            } else {
10917                self.generate_identifier(&g.securable)?;
10918            }
10919        }
10920
10921        // Function parameter types (if present)
10922        if !g.function_params.is_empty() {
10923            self.write("(");
10924            for (i, param) in g.function_params.iter().enumerate() {
10925                if i > 0 {
10926                    self.write(", ");
10927                }
10928                self.write(param);
10929            }
10930            self.write(")");
10931        }
10932
10933        self.write_space();
10934        self.write_keyword("TO");
10935        self.write_space();
10936
10937        // Principals
10938        for (i, principal) in g.principals.iter().enumerate() {
10939            if i > 0 {
10940                self.write(", ");
10941            }
10942            if principal.is_role {
10943                self.write_keyword("ROLE");
10944                self.write_space();
10945            } else if principal.is_group {
10946                self.write_keyword("GROUP");
10947                self.write_space();
10948            }
10949            self.generate_identifier(&principal.name)?;
10950        }
10951
10952        // WITH GRANT OPTION
10953        if g.grant_option {
10954            self.write_space();
10955            self.write_keyword("WITH GRANT OPTION");
10956        }
10957
10958        // TSQL: AS principal
10959        if let Some(ref principal) = g.as_principal {
10960            self.write_space();
10961            self.write_keyword("AS");
10962            self.write_space();
10963            self.generate_identifier(principal)?;
10964        }
10965
10966        Ok(())
10967    }
10968
10969    fn generate_revoke(&mut self, r: &Revoke) -> Result<()> {
10970        self.write_keyword("REVOKE");
10971        self.write_space();
10972
10973        // GRANT OPTION FOR
10974        if r.grant_option {
10975            self.write_keyword("GRANT OPTION FOR");
10976            self.write_space();
10977        }
10978
10979        // Privileges (with optional column lists)
10980        for (i, privilege) in r.privileges.iter().enumerate() {
10981            if i > 0 {
10982                self.write(", ");
10983            }
10984            self.write_keyword(&privilege.name);
10985            // Output column list if present: SELECT(col1, col2)
10986            if !privilege.columns.is_empty() {
10987                self.write("(");
10988                for (j, col) in privilege.columns.iter().enumerate() {
10989                    if j > 0 {
10990                        self.write(", ");
10991                    }
10992                    self.write(col);
10993                }
10994                self.write(")");
10995            }
10996        }
10997
10998        self.write_space();
10999        self.write_keyword("ON");
11000        self.write_space();
11001
11002        // Object kind
11003        if let Some(kind) = &r.kind {
11004            self.write_keyword(kind);
11005            self.write_space();
11006        }
11007
11008        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
11009        {
11010            use crate::dialects::DialectType;
11011            let should_upper = matches!(
11012                self.config.dialect,
11013                Some(DialectType::PostgreSQL)
11014                    | Some(DialectType::CockroachDB)
11015                    | Some(DialectType::Materialize)
11016                    | Some(DialectType::RisingWave)
11017            ) && (r.kind.as_deref() == Some("FUNCTION")
11018                || r.kind.as_deref() == Some("PROCEDURE"));
11019            if should_upper {
11020                use crate::expressions::Identifier;
11021                let upper_id = Identifier {
11022                    name: r.securable.name.to_uppercase(),
11023                    quoted: r.securable.quoted,
11024                    ..r.securable.clone()
11025                };
11026                self.generate_identifier(&upper_id)?;
11027            } else {
11028                self.generate_identifier(&r.securable)?;
11029            }
11030        }
11031
11032        // Function parameter types (if present)
11033        if !r.function_params.is_empty() {
11034            self.write("(");
11035            for (i, param) in r.function_params.iter().enumerate() {
11036                if i > 0 {
11037                    self.write(", ");
11038                }
11039                self.write(param);
11040            }
11041            self.write(")");
11042        }
11043
11044        self.write_space();
11045        self.write_keyword("FROM");
11046        self.write_space();
11047
11048        // Principals
11049        for (i, principal) in r.principals.iter().enumerate() {
11050            if i > 0 {
11051                self.write(", ");
11052            }
11053            if principal.is_role {
11054                self.write_keyword("ROLE");
11055                self.write_space();
11056            } else if principal.is_group {
11057                self.write_keyword("GROUP");
11058                self.write_space();
11059            }
11060            self.generate_identifier(&principal.name)?;
11061        }
11062
11063        // CASCADE or RESTRICT
11064        if r.cascade {
11065            self.write_space();
11066            self.write_keyword("CASCADE");
11067        } else if r.restrict {
11068            self.write_space();
11069            self.write_keyword("RESTRICT");
11070        }
11071
11072        Ok(())
11073    }
11074
11075    fn generate_comment(&mut self, c: &Comment) -> Result<()> {
11076        self.write_keyword("COMMENT");
11077
11078        // IF EXISTS
11079        if c.exists {
11080            self.write_space();
11081            self.write_keyword("IF EXISTS");
11082        }
11083
11084        self.write_space();
11085        self.write_keyword("ON");
11086
11087        // MATERIALIZED
11088        if c.materialized {
11089            self.write_space();
11090            self.write_keyword("MATERIALIZED");
11091        }
11092
11093        self.write_space();
11094        self.write_keyword(&c.kind);
11095        self.write_space();
11096
11097        // Object name
11098        self.generate_expression(&c.this)?;
11099
11100        self.write_space();
11101        self.write_keyword("IS");
11102        self.write_space();
11103
11104        // Comment expression
11105        self.generate_expression(&c.expression)?;
11106
11107        Ok(())
11108    }
11109
11110    fn generate_set_statement(&mut self, s: &SetStatement) -> Result<()> {
11111        self.write_keyword("SET");
11112
11113        for (i, item) in s.items.iter().enumerate() {
11114            if i > 0 {
11115                self.write(",");
11116            }
11117            self.write_space();
11118
11119            // Kind modifier (GLOBAL, LOCAL, SESSION, PERSIST, PERSIST_ONLY)
11120            if let Some(ref kind) = item.kind {
11121                self.write_keyword(kind);
11122                self.write_space();
11123            }
11124
11125            // Check for special SET forms by name
11126            let name_str = match &item.name {
11127                Expression::Identifier(id) => Some(id.name.as_str()),
11128                _ => None,
11129            };
11130
11131            let is_transaction = name_str == Some("TRANSACTION");
11132            let is_character_set = name_str == Some("CHARACTER SET");
11133            let is_names = name_str == Some("NAMES");
11134            let is_collate = name_str == Some("COLLATE");
11135            let has_variable_kind = item.kind.as_deref() == Some("VARIABLE");
11136            let name_has_variable_prefix = name_str.map_or(false, |n| n.starts_with("VARIABLE "));
11137            let is_variable = has_variable_kind || name_has_variable_prefix;
11138            let is_value_only =
11139                matches!(&item.value, Expression::Identifier(id) if id.name.is_empty());
11140
11141            if is_transaction {
11142                // Output: SET [GLOBAL|SESSION] TRANSACTION <characteristics>
11143                self.write_keyword("TRANSACTION");
11144                if let Expression::Identifier(id) = &item.value {
11145                    if !id.name.is_empty() {
11146                        self.write_space();
11147                        self.write(&id.name);
11148                    }
11149                }
11150            } else if is_character_set {
11151                // Output: SET CHARACTER SET <charset>
11152                self.write_keyword("CHARACTER SET");
11153                self.write_space();
11154                self.generate_set_value(&item.value)?;
11155            } else if is_names {
11156                // Output: SET NAMES <charset>
11157                self.write_keyword("NAMES");
11158                self.write_space();
11159                self.generate_set_value(&item.value)?;
11160            } else if is_collate {
11161                // Output: COLLATE <collation> (part of SET NAMES ... COLLATE ...)
11162                self.write_keyword("COLLATE");
11163                self.write_space();
11164                self.generate_set_value(&item.value)?;
11165            } else if is_variable {
11166                // Output: SET [VARIABLE] <name> = <value>
11167                // If kind=VARIABLE, the keyword was already written above.
11168                // If name has VARIABLE prefix, write VARIABLE keyword for DuckDB target only.
11169                if name_has_variable_prefix && !has_variable_kind {
11170                    if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
11171                        self.write_keyword("VARIABLE");
11172                        self.write_space();
11173                    }
11174                }
11175                // Extract actual variable name (strip VARIABLE prefix if present)
11176                if let Some(ns) = name_str {
11177                    let var_name = if name_has_variable_prefix {
11178                        &ns["VARIABLE ".len()..]
11179                    } else {
11180                        ns
11181                    };
11182                    self.write(var_name);
11183                } else {
11184                    self.generate_expression(&item.name)?;
11185                }
11186                self.write(" = ");
11187                self.generate_set_value(&item.value)?;
11188            } else if is_value_only {
11189                // SET <name> ON/OFF without = (TSQL: SET XACT_ABORT ON)
11190                self.generate_expression(&item.name)?;
11191            } else if item.no_equals && matches!(self.config.dialect, Some(DialectType::TSQL)) {
11192                // SET key value without = (TSQL style)
11193                self.generate_expression(&item.name)?;
11194                self.write_space();
11195                self.generate_set_value(&item.value)?;
11196            } else {
11197                // Standard: variable = value
11198                // SET item names should not be quoted (they are config parameter names, not column refs)
11199                match &item.name {
11200                    Expression::Identifier(id) => {
11201                        self.write(&id.name);
11202                    }
11203                    _ => {
11204                        self.generate_expression(&item.name)?;
11205                    }
11206                }
11207                self.write(" = ");
11208                self.generate_set_value(&item.value)?;
11209            }
11210        }
11211
11212        Ok(())
11213    }
11214
11215    /// Generate a SET statement value, writing keyword values (DEFAULT, ON, OFF)
11216    /// directly to avoid reserved keyword quoting.
11217    fn generate_set_value(&mut self, value: &Expression) -> Result<()> {
11218        if let Expression::Identifier(id) = value {
11219            match id.name.as_str() {
11220                "DEFAULT" | "ON" | "OFF" => {
11221                    self.write_keyword(&id.name);
11222                    return Ok(());
11223                }
11224                _ => {}
11225            }
11226        }
11227        self.generate_expression(value)
11228    }
11229
11230    // ==================== Phase 4: Additional DDL Generation ====================
11231
11232    fn generate_alter_view(&mut self, av: &AlterView) -> Result<()> {
11233        self.write_keyword("ALTER");
11234        // MySQL modifiers before VIEW
11235        if let Some(ref algorithm) = av.algorithm {
11236            self.write_space();
11237            self.write_keyword("ALGORITHM");
11238            self.write(" = ");
11239            self.write_keyword(algorithm);
11240        }
11241        if let Some(ref definer) = av.definer {
11242            self.write_space();
11243            self.write_keyword("DEFINER");
11244            self.write(" = ");
11245            self.write(definer);
11246        }
11247        if let Some(ref sql_security) = av.sql_security {
11248            self.write_space();
11249            self.write_keyword("SQL SECURITY");
11250            self.write(" = ");
11251            self.write_keyword(sql_security);
11252        }
11253        self.write_space();
11254        self.write_keyword("VIEW");
11255        self.write_space();
11256        self.generate_table(&av.name)?;
11257
11258        // Hive: Column aliases with optional COMMENT
11259        if !av.columns.is_empty() {
11260            self.write(" (");
11261            for (i, col) in av.columns.iter().enumerate() {
11262                if i > 0 {
11263                    self.write(", ");
11264                }
11265                self.generate_identifier(&col.name)?;
11266                if let Some(ref comment) = col.comment {
11267                    self.write_space();
11268                    self.write_keyword("COMMENT");
11269                    self.write(" ");
11270                    self.generate_string_literal(comment)?;
11271                }
11272            }
11273            self.write(")");
11274        }
11275
11276        // TSQL: WITH option before actions
11277        if let Some(ref opt) = av.with_option {
11278            self.write_space();
11279            self.write_keyword("WITH");
11280            self.write_space();
11281            self.write_keyword(opt);
11282        }
11283
11284        for action in &av.actions {
11285            self.write_space();
11286            match action {
11287                AlterViewAction::Rename(new_name) => {
11288                    self.write_keyword("RENAME TO");
11289                    self.write_space();
11290                    self.generate_table(new_name)?;
11291                }
11292                AlterViewAction::OwnerTo(owner) => {
11293                    self.write_keyword("OWNER TO");
11294                    self.write_space();
11295                    self.generate_identifier(owner)?;
11296                }
11297                AlterViewAction::SetSchema(schema) => {
11298                    self.write_keyword("SET SCHEMA");
11299                    self.write_space();
11300                    self.generate_identifier(schema)?;
11301                }
11302                AlterViewAction::SetAuthorization(auth) => {
11303                    self.write_keyword("SET AUTHORIZATION");
11304                    self.write_space();
11305                    self.write(auth);
11306                }
11307                AlterViewAction::AlterColumn { name, action } => {
11308                    self.write_keyword("ALTER COLUMN");
11309                    self.write_space();
11310                    self.generate_identifier(name)?;
11311                    self.write_space();
11312                    self.generate_alter_column_action(action)?;
11313                }
11314                AlterViewAction::AsSelect(query) => {
11315                    self.write_keyword("AS");
11316                    self.write_space();
11317                    self.generate_expression(query)?;
11318                }
11319                AlterViewAction::SetTblproperties(props) => {
11320                    self.write_keyword("SET TBLPROPERTIES");
11321                    self.write(" (");
11322                    for (i, (key, value)) in props.iter().enumerate() {
11323                        if i > 0 {
11324                            self.write(", ");
11325                        }
11326                        self.generate_string_literal(key)?;
11327                        self.write("=");
11328                        self.generate_string_literal(value)?;
11329                    }
11330                    self.write(")");
11331                }
11332                AlterViewAction::UnsetTblproperties(keys) => {
11333                    self.write_keyword("UNSET TBLPROPERTIES");
11334                    self.write(" (");
11335                    for (i, key) in keys.iter().enumerate() {
11336                        if i > 0 {
11337                            self.write(", ");
11338                        }
11339                        self.generate_string_literal(key)?;
11340                    }
11341                    self.write(")");
11342                }
11343            }
11344        }
11345
11346        Ok(())
11347    }
11348
11349    fn generate_alter_index(&mut self, ai: &AlterIndex) -> Result<()> {
11350        self.write_keyword("ALTER INDEX");
11351        self.write_space();
11352        self.generate_identifier(&ai.name)?;
11353
11354        if let Some(table) = &ai.table {
11355            self.write_space();
11356            self.write_keyword("ON");
11357            self.write_space();
11358            self.generate_table(table)?;
11359        }
11360
11361        for action in &ai.actions {
11362            self.write_space();
11363            match action {
11364                AlterIndexAction::Rename(new_name) => {
11365                    self.write_keyword("RENAME TO");
11366                    self.write_space();
11367                    self.generate_identifier(new_name)?;
11368                }
11369                AlterIndexAction::SetTablespace(tablespace) => {
11370                    self.write_keyword("SET TABLESPACE");
11371                    self.write_space();
11372                    self.generate_identifier(tablespace)?;
11373                }
11374                AlterIndexAction::Visible(visible) => {
11375                    if *visible {
11376                        self.write_keyword("VISIBLE");
11377                    } else {
11378                        self.write_keyword("INVISIBLE");
11379                    }
11380                }
11381            }
11382        }
11383
11384        Ok(())
11385    }
11386
11387    fn generate_create_schema(&mut self, cs: &CreateSchema) -> Result<()> {
11388        // Output leading comments
11389        for comment in &cs.leading_comments {
11390            self.write_formatted_comment(comment);
11391            self.write_space();
11392        }
11393
11394        // Athena: CREATE SCHEMA uses Hive engine (backticks)
11395        let saved_athena_hive_context = self.athena_hive_context;
11396        if matches!(
11397            self.config.dialect,
11398            Some(crate::dialects::DialectType::Athena)
11399        ) {
11400            self.athena_hive_context = true;
11401        }
11402
11403        self.write_keyword("CREATE SCHEMA");
11404
11405        if cs.if_not_exists {
11406            self.write_space();
11407            self.write_keyword("IF NOT EXISTS");
11408        }
11409
11410        self.write_space();
11411        self.generate_identifier(&cs.name)?;
11412
11413        if let Some(ref clone_src) = cs.clone_from {
11414            self.write_keyword(" CLONE ");
11415            self.generate_identifier(clone_src)?;
11416        }
11417
11418        if let Some(ref at_clause) = cs.at_clause {
11419            self.write_space();
11420            self.generate_expression(at_clause)?;
11421        }
11422
11423        if let Some(auth) = &cs.authorization {
11424            self.write_space();
11425            self.write_keyword("AUTHORIZATION");
11426            self.write_space();
11427            self.generate_identifier(auth)?;
11428        }
11429
11430        // Generate schema properties (e.g., DEFAULT COLLATE or WITH (props))
11431        // Separate WITH properties from other properties
11432        let with_properties: Vec<_> = cs
11433            .properties
11434            .iter()
11435            .filter(|p| matches!(p, Expression::Property(_)))
11436            .collect();
11437        let other_properties: Vec<_> = cs
11438            .properties
11439            .iter()
11440            .filter(|p| !matches!(p, Expression::Property(_)))
11441            .collect();
11442
11443        // Generate WITH (props) if we have Property expressions
11444        if !with_properties.is_empty() {
11445            self.write_space();
11446            self.write_keyword("WITH");
11447            self.write(" (");
11448            for (i, prop) in with_properties.iter().enumerate() {
11449                if i > 0 {
11450                    self.write(", ");
11451                }
11452                self.generate_expression(prop)?;
11453            }
11454            self.write(")");
11455        }
11456
11457        // Generate other properties (like DEFAULT COLLATE)
11458        for prop in other_properties {
11459            self.write_space();
11460            self.generate_expression(prop)?;
11461        }
11462
11463        // Restore Athena Hive context
11464        self.athena_hive_context = saved_athena_hive_context;
11465
11466        Ok(())
11467    }
11468
11469    fn generate_drop_schema(&mut self, ds: &DropSchema) -> Result<()> {
11470        self.write_keyword("DROP SCHEMA");
11471
11472        if ds.if_exists {
11473            self.write_space();
11474            self.write_keyword("IF EXISTS");
11475        }
11476
11477        self.write_space();
11478        self.generate_identifier(&ds.name)?;
11479
11480        if ds.cascade {
11481            self.write_space();
11482            self.write_keyword("CASCADE");
11483        }
11484
11485        Ok(())
11486    }
11487
11488    fn generate_drop_namespace(&mut self, dn: &DropNamespace) -> Result<()> {
11489        self.write_keyword("DROP NAMESPACE");
11490
11491        if dn.if_exists {
11492            self.write_space();
11493            self.write_keyword("IF EXISTS");
11494        }
11495
11496        self.write_space();
11497        self.generate_identifier(&dn.name)?;
11498
11499        if dn.cascade {
11500            self.write_space();
11501            self.write_keyword("CASCADE");
11502        }
11503
11504        Ok(())
11505    }
11506
11507    fn generate_create_database(&mut self, cd: &CreateDatabase) -> Result<()> {
11508        self.write_keyword("CREATE DATABASE");
11509
11510        if cd.if_not_exists {
11511            self.write_space();
11512            self.write_keyword("IF NOT EXISTS");
11513        }
11514
11515        self.write_space();
11516        self.generate_identifier(&cd.name)?;
11517
11518        if let Some(ref clone_src) = cd.clone_from {
11519            self.write_keyword(" CLONE ");
11520            self.generate_identifier(clone_src)?;
11521        }
11522
11523        // AT/BEFORE clause for time travel (Snowflake)
11524        if let Some(ref at_clause) = cd.at_clause {
11525            self.write_space();
11526            self.generate_expression(at_clause)?;
11527        }
11528
11529        for option in &cd.options {
11530            self.write_space();
11531            match option {
11532                DatabaseOption::CharacterSet(charset) => {
11533                    self.write_keyword("CHARACTER SET");
11534                    self.write(" = ");
11535                    self.write(&format!("'{}'", charset));
11536                }
11537                DatabaseOption::Collate(collate) => {
11538                    self.write_keyword("COLLATE");
11539                    self.write(" = ");
11540                    self.write(&format!("'{}'", collate));
11541                }
11542                DatabaseOption::Owner(owner) => {
11543                    self.write_keyword("OWNER");
11544                    self.write(" = ");
11545                    self.generate_identifier(owner)?;
11546                }
11547                DatabaseOption::Template(template) => {
11548                    self.write_keyword("TEMPLATE");
11549                    self.write(" = ");
11550                    self.generate_identifier(template)?;
11551                }
11552                DatabaseOption::Encoding(encoding) => {
11553                    self.write_keyword("ENCODING");
11554                    self.write(" = ");
11555                    self.write(&format!("'{}'", encoding));
11556                }
11557                DatabaseOption::Location(location) => {
11558                    self.write_keyword("LOCATION");
11559                    self.write(" = ");
11560                    self.write(&format!("'{}'", location));
11561                }
11562            }
11563        }
11564
11565        Ok(())
11566    }
11567
11568    fn generate_drop_database(&mut self, dd: &DropDatabase) -> Result<()> {
11569        self.write_keyword("DROP DATABASE");
11570
11571        if dd.if_exists {
11572            self.write_space();
11573            self.write_keyword("IF EXISTS");
11574        }
11575
11576        self.write_space();
11577        self.generate_identifier(&dd.name)?;
11578
11579        Ok(())
11580    }
11581
11582    fn generate_create_function(&mut self, cf: &CreateFunction) -> Result<()> {
11583        self.write_keyword("CREATE");
11584
11585        if cf.or_replace {
11586            self.write_space();
11587            self.write_keyword("OR REPLACE");
11588        }
11589
11590        if cf.temporary {
11591            self.write_space();
11592            self.write_keyword("TEMPORARY");
11593        }
11594
11595        self.write_space();
11596        if cf.is_table_function {
11597            self.write_keyword("TABLE FUNCTION");
11598        } else {
11599            self.write_keyword("FUNCTION");
11600        }
11601
11602        if cf.if_not_exists {
11603            self.write_space();
11604            self.write_keyword("IF NOT EXISTS");
11605        }
11606
11607        self.write_space();
11608        self.generate_table(&cf.name)?;
11609        if cf.has_parens {
11610            let func_multiline = self.config.pretty
11611                && matches!(
11612                    self.config.dialect,
11613                    Some(crate::dialects::DialectType::TSQL)
11614                        | Some(crate::dialects::DialectType::Fabric)
11615                )
11616                && !cf.parameters.is_empty();
11617            if func_multiline {
11618                self.write("(\n");
11619                self.indent_level += 2;
11620                self.write_indent();
11621                self.generate_function_parameters(&cf.parameters)?;
11622                self.write("\n");
11623                self.indent_level -= 2;
11624                self.write(")");
11625            } else {
11626                self.write("(");
11627                self.generate_function_parameters(&cf.parameters)?;
11628                self.write(")");
11629            }
11630        }
11631
11632        // Output RETURNS clause (always comes first after parameters)
11633        // BigQuery and TSQL use multiline formatting for CREATE FUNCTION structure
11634        let use_multiline = self.config.pretty
11635            && matches!(
11636                self.config.dialect,
11637                Some(crate::dialects::DialectType::BigQuery)
11638                    | Some(crate::dialects::DialectType::TSQL)
11639                    | Some(crate::dialects::DialectType::Fabric)
11640            );
11641
11642        if cf.language_first {
11643            // LANGUAGE first, then SQL data access, then RETURNS
11644            if let Some(lang) = &cf.language {
11645                if use_multiline {
11646                    self.write_newline();
11647                } else {
11648                    self.write_space();
11649                }
11650                self.write_keyword("LANGUAGE");
11651                self.write_space();
11652                self.write(lang);
11653            }
11654
11655            // SQL data access comes after LANGUAGE in this case
11656            if let Some(sql_data) = &cf.sql_data_access {
11657                self.write_space();
11658                match sql_data {
11659                    SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
11660                    SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
11661                    SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
11662                    SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
11663                }
11664            }
11665
11666            if let Some(ref rtb) = cf.returns_table_body {
11667                if use_multiline {
11668                    self.write_newline();
11669                } else {
11670                    self.write_space();
11671                }
11672                self.write_keyword("RETURNS");
11673                self.write_space();
11674                self.write(rtb);
11675            } else if let Some(return_type) = &cf.return_type {
11676                if use_multiline {
11677                    self.write_newline();
11678                } else {
11679                    self.write_space();
11680                }
11681                self.write_keyword("RETURNS");
11682                self.write_space();
11683                self.generate_data_type(return_type)?;
11684            }
11685        } else {
11686            // RETURNS first (default)
11687            // DuckDB macros: skip RETURNS output (empty marker in returns_table_body means TABLE return)
11688            let is_duckdb = matches!(
11689                self.config.dialect,
11690                Some(crate::dialects::DialectType::DuckDB)
11691            );
11692            if let Some(ref rtb) = cf.returns_table_body {
11693                if !(is_duckdb && rtb.is_empty()) {
11694                    if use_multiline {
11695                        self.write_newline();
11696                    } else {
11697                        self.write_space();
11698                    }
11699                    self.write_keyword("RETURNS");
11700                    self.write_space();
11701                    self.write(rtb);
11702                }
11703            } else if let Some(return_type) = &cf.return_type {
11704                if use_multiline {
11705                    self.write_newline();
11706                } else {
11707                    self.write_space();
11708                }
11709                self.write_keyword("RETURNS");
11710                self.write_space();
11711                self.generate_data_type(return_type)?;
11712            }
11713        }
11714
11715        // If we have property_order, use it to output properties in original order
11716        if !cf.property_order.is_empty() {
11717            // For BigQuery, OPTIONS must come before AS - reorder if needed
11718            let is_bigquery = matches!(
11719                self.config.dialect,
11720                Some(crate::dialects::DialectType::BigQuery)
11721            );
11722            let property_order = if is_bigquery {
11723                // Move Options before As if both are present
11724                let mut reordered = Vec::new();
11725                let mut has_as = false;
11726                let mut has_options = false;
11727                for prop in &cf.property_order {
11728                    match prop {
11729                        FunctionPropertyKind::As => has_as = true,
11730                        FunctionPropertyKind::Options => has_options = true,
11731                        _ => {}
11732                    }
11733                }
11734                if has_as && has_options {
11735                    // Output all props except As and Options, then Options, then As
11736                    for prop in &cf.property_order {
11737                        if *prop != FunctionPropertyKind::As
11738                            && *prop != FunctionPropertyKind::Options
11739                        {
11740                            reordered.push(*prop);
11741                        }
11742                    }
11743                    reordered.push(FunctionPropertyKind::Options);
11744                    reordered.push(FunctionPropertyKind::As);
11745                    reordered
11746                } else {
11747                    cf.property_order.clone()
11748                }
11749            } else {
11750                cf.property_order.clone()
11751            };
11752
11753            for prop in &property_order {
11754                match prop {
11755                    FunctionPropertyKind::Set => {
11756                        self.generate_function_set_options(cf)?;
11757                    }
11758                    FunctionPropertyKind::As => {
11759                        self.generate_function_body(cf)?;
11760                    }
11761                    FunctionPropertyKind::Language => {
11762                        if !cf.language_first {
11763                            // Only output here if not already output above
11764                            if let Some(lang) = &cf.language {
11765                                // Only BigQuery uses multiline formatting
11766                                let use_multiline = self.config.pretty
11767                                    && matches!(
11768                                        self.config.dialect,
11769                                        Some(crate::dialects::DialectType::BigQuery)
11770                                    );
11771                                if use_multiline {
11772                                    self.write_newline();
11773                                } else {
11774                                    self.write_space();
11775                                }
11776                                self.write_keyword("LANGUAGE");
11777                                self.write_space();
11778                                self.write(lang);
11779                            }
11780                        }
11781                    }
11782                    FunctionPropertyKind::Determinism => {
11783                        self.generate_function_determinism(cf)?;
11784                    }
11785                    FunctionPropertyKind::NullInput => {
11786                        self.generate_function_null_input(cf)?;
11787                    }
11788                    FunctionPropertyKind::Security => {
11789                        self.generate_function_security(cf)?;
11790                    }
11791                    FunctionPropertyKind::SqlDataAccess => {
11792                        if !cf.language_first {
11793                            // Only output here if not already output above
11794                            self.generate_function_sql_data_access(cf)?;
11795                        }
11796                    }
11797                    FunctionPropertyKind::Options => {
11798                        if !cf.options.is_empty() {
11799                            self.write_space();
11800                            self.generate_options_clause(&cf.options)?;
11801                        }
11802                    }
11803                    FunctionPropertyKind::Environment => {
11804                        if !cf.environment.is_empty() {
11805                            self.write_space();
11806                            self.generate_environment_clause(&cf.environment)?;
11807                        }
11808                    }
11809                }
11810            }
11811
11812            // Output OPTIONS if not tracked in property_order (legacy)
11813            if !cf.options.is_empty() && !cf.property_order.contains(&FunctionPropertyKind::Options)
11814            {
11815                self.write_space();
11816                self.generate_options_clause(&cf.options)?;
11817            }
11818
11819            // Output ENVIRONMENT if not tracked in property_order (legacy)
11820            if !cf.environment.is_empty()
11821                && !cf
11822                    .property_order
11823                    .contains(&FunctionPropertyKind::Environment)
11824            {
11825                self.write_space();
11826                self.generate_environment_clause(&cf.environment)?;
11827            }
11828        } else {
11829            // Legacy behavior when property_order is empty
11830            // BigQuery: DETERMINISTIC/NOT DETERMINISTIC comes before LANGUAGE
11831            if matches!(
11832                self.config.dialect,
11833                Some(crate::dialects::DialectType::BigQuery)
11834            ) {
11835                self.generate_function_determinism(cf)?;
11836            }
11837
11838            // Only BigQuery uses multiline formatting for CREATE FUNCTION structure
11839            let use_multiline = self.config.pretty
11840                && matches!(
11841                    self.config.dialect,
11842                    Some(crate::dialects::DialectType::BigQuery)
11843                );
11844
11845            if !cf.language_first {
11846                if let Some(lang) = &cf.language {
11847                    if use_multiline {
11848                        self.write_newline();
11849                    } else {
11850                        self.write_space();
11851                    }
11852                    self.write_keyword("LANGUAGE");
11853                    self.write_space();
11854                    self.write(lang);
11855                }
11856
11857                // SQL data access characteristic comes after LANGUAGE
11858                self.generate_function_sql_data_access(cf)?;
11859            }
11860
11861            // For non-BigQuery dialects, output DETERMINISTIC/IMMUTABLE/VOLATILE here
11862            if !matches!(
11863                self.config.dialect,
11864                Some(crate::dialects::DialectType::BigQuery)
11865            ) {
11866                self.generate_function_determinism(cf)?;
11867            }
11868
11869            self.generate_function_null_input(cf)?;
11870            self.generate_function_security(cf)?;
11871            self.generate_function_set_options(cf)?;
11872
11873            // BigQuery: OPTIONS (key=value, ...) - comes before AS
11874            if !cf.options.is_empty() {
11875                self.write_space();
11876                self.generate_options_clause(&cf.options)?;
11877            }
11878
11879            // Databricks: ENVIRONMENT (dependencies = '...', ...) - comes before AS
11880            if !cf.environment.is_empty() {
11881                self.write_space();
11882                self.generate_environment_clause(&cf.environment)?;
11883            }
11884
11885            self.generate_function_body(cf)?;
11886        }
11887
11888        Ok(())
11889    }
11890
11891    /// Generate SET options for CREATE FUNCTION
11892    fn generate_function_set_options(&mut self, cf: &CreateFunction) -> Result<()> {
11893        for opt in &cf.set_options {
11894            self.write_space();
11895            self.write_keyword("SET");
11896            self.write_space();
11897            self.write(&opt.name);
11898            match &opt.value {
11899                FunctionSetValue::Value { value, use_to } => {
11900                    if *use_to {
11901                        self.write(" TO ");
11902                    } else {
11903                        self.write(" = ");
11904                    }
11905                    self.write(value);
11906                }
11907                FunctionSetValue::FromCurrent => {
11908                    self.write_space();
11909                    self.write_keyword("FROM CURRENT");
11910                }
11911            }
11912        }
11913        Ok(())
11914    }
11915
11916    /// Generate function body (AS clause)
11917    fn generate_function_body(&mut self, cf: &CreateFunction) -> Result<()> {
11918        if let Some(body) = &cf.body {
11919            // AS stays on same line as previous content (e.g., LANGUAGE js AS)
11920            self.write_space();
11921            // Only BigQuery uses multiline formatting for CREATE FUNCTION body
11922            let use_multiline = self.config.pretty
11923                && matches!(
11924                    self.config.dialect,
11925                    Some(crate::dialects::DialectType::BigQuery)
11926                );
11927            match body {
11928                FunctionBody::Block(block) => {
11929                    self.write_keyword("AS");
11930                    if matches!(
11931                        self.config.dialect,
11932                        Some(crate::dialects::DialectType::TSQL)
11933                    ) {
11934                        self.write(" BEGIN ");
11935                        self.write(block);
11936                        self.write(" END");
11937                    } else if matches!(
11938                        self.config.dialect,
11939                        Some(crate::dialects::DialectType::PostgreSQL)
11940                    ) {
11941                        self.write(" $$");
11942                        self.write(block);
11943                        self.write("$$");
11944                    } else {
11945                        // Escape content for single-quoted output
11946                        let escaped = self.escape_block_for_single_quote(block);
11947                        // In BigQuery pretty mode, body content goes on new line
11948                        if use_multiline {
11949                            self.write_newline();
11950                        } else {
11951                            self.write(" ");
11952                        }
11953                        self.write("'");
11954                        self.write(&escaped);
11955                        self.write("'");
11956                    }
11957                }
11958                FunctionBody::StringLiteral(s) => {
11959                    self.write_keyword("AS");
11960                    // In BigQuery pretty mode, body content goes on new line
11961                    if use_multiline {
11962                        self.write_newline();
11963                    } else {
11964                        self.write(" ");
11965                    }
11966                    self.write("'");
11967                    self.write(s);
11968                    self.write("'");
11969                }
11970                FunctionBody::Expression(expr) => {
11971                    self.write_keyword("AS");
11972                    self.write_space();
11973                    self.generate_expression(expr)?;
11974                }
11975                FunctionBody::External(name) => {
11976                    self.write_keyword("EXTERNAL NAME");
11977                    self.write(" '");
11978                    self.write(name);
11979                    self.write("'");
11980                }
11981                FunctionBody::Return(expr) => {
11982                    if matches!(
11983                        self.config.dialect,
11984                        Some(crate::dialects::DialectType::DuckDB)
11985                    ) {
11986                        // DuckDB macro syntax: AS [TABLE] expression (no RETURN keyword)
11987                        self.write_keyword("AS");
11988                        self.write_space();
11989                        // Empty returns_table_body signals TABLE return
11990                        if cf.returns_table_body.is_some() {
11991                            self.write_keyword("TABLE");
11992                            self.write_space();
11993                        }
11994                        self.generate_expression(expr)?;
11995                    } else {
11996                        if self.config.create_function_return_as {
11997                            self.write_keyword("AS");
11998                            // TSQL pretty: newline between AS and RETURN
11999                            if self.config.pretty
12000                                && matches!(
12001                                    self.config.dialect,
12002                                    Some(crate::dialects::DialectType::TSQL)
12003                                        | Some(crate::dialects::DialectType::Fabric)
12004                                )
12005                            {
12006                                self.write_newline();
12007                            } else {
12008                                self.write_space();
12009                            }
12010                        }
12011                        self.write_keyword("RETURN");
12012                        self.write_space();
12013                        self.generate_expression(expr)?;
12014                    }
12015                }
12016                FunctionBody::Statements(stmts) => {
12017                    self.write_keyword("AS");
12018                    self.write(" BEGIN ");
12019                    for (i, stmt) in stmts.iter().enumerate() {
12020                        if i > 0 {
12021                            self.write(" ");
12022                        }
12023                        self.generate_expression(stmt)?;
12024                    }
12025                    self.write(" END");
12026                }
12027                FunctionBody::DollarQuoted { content, tag } => {
12028                    self.write_keyword("AS");
12029                    self.write(" ");
12030                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
12031                    let supports_dollar_quoting = matches!(
12032                        self.config.dialect,
12033                        Some(crate::dialects::DialectType::PostgreSQL)
12034                            | Some(crate::dialects::DialectType::Databricks)
12035                            | Some(crate::dialects::DialectType::Redshift)
12036                            | Some(crate::dialects::DialectType::DuckDB)
12037                    );
12038                    if supports_dollar_quoting {
12039                        // Output in dollar-quoted format
12040                        self.write("$");
12041                        if let Some(t) = tag {
12042                            self.write(t);
12043                        }
12044                        self.write("$");
12045                        self.write(content);
12046                        self.write("$");
12047                        if let Some(t) = tag {
12048                            self.write(t);
12049                        }
12050                        self.write("$");
12051                    } else {
12052                        // Convert to single-quoted string for other dialects
12053                        let escaped = self.escape_block_for_single_quote(content);
12054                        self.write("'");
12055                        self.write(&escaped);
12056                        self.write("'");
12057                    }
12058                }
12059            }
12060        }
12061        Ok(())
12062    }
12063
12064    /// Generate determinism clause (IMMUTABLE/VOLATILE/DETERMINISTIC)
12065    fn generate_function_determinism(&mut self, cf: &CreateFunction) -> Result<()> {
12066        if let Some(det) = cf.deterministic {
12067            self.write_space();
12068            if matches!(
12069                self.config.dialect,
12070                Some(crate::dialects::DialectType::BigQuery)
12071            ) {
12072                // BigQuery uses DETERMINISTIC/NOT DETERMINISTIC
12073                if det {
12074                    self.write_keyword("DETERMINISTIC");
12075                } else {
12076                    self.write_keyword("NOT DETERMINISTIC");
12077                }
12078            } else {
12079                // PostgreSQL and others use IMMUTABLE/VOLATILE
12080                if det {
12081                    self.write_keyword("IMMUTABLE");
12082                } else {
12083                    self.write_keyword("VOLATILE");
12084                }
12085            }
12086        }
12087        Ok(())
12088    }
12089
12090    /// Generate null input handling clause
12091    fn generate_function_null_input(&mut self, cf: &CreateFunction) -> Result<()> {
12092        if let Some(returns_null) = cf.returns_null_on_null_input {
12093            self.write_space();
12094            if returns_null {
12095                if cf.strict {
12096                    self.write_keyword("STRICT");
12097                } else {
12098                    self.write_keyword("RETURNS NULL ON NULL INPUT");
12099                }
12100            } else {
12101                self.write_keyword("CALLED ON NULL INPUT");
12102            }
12103        }
12104        Ok(())
12105    }
12106
12107    /// Generate security clause
12108    fn generate_function_security(&mut self, cf: &CreateFunction) -> Result<()> {
12109        if let Some(security) = &cf.security {
12110            self.write_space();
12111            self.write_keyword("SECURITY");
12112            self.write_space();
12113            match security {
12114                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
12115                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
12116                FunctionSecurity::None => self.write_keyword("NONE"),
12117            }
12118        }
12119        Ok(())
12120    }
12121
12122    /// Generate SQL data access clause
12123    fn generate_function_sql_data_access(&mut self, cf: &CreateFunction) -> Result<()> {
12124        if let Some(sql_data) = &cf.sql_data_access {
12125            self.write_space();
12126            match sql_data {
12127                SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
12128                SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
12129                SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
12130                SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
12131            }
12132        }
12133        Ok(())
12134    }
12135
12136    fn generate_function_parameters(&mut self, params: &[FunctionParameter]) -> Result<()> {
12137        for (i, param) in params.iter().enumerate() {
12138            if i > 0 {
12139                self.write(", ");
12140            }
12141
12142            if let Some(mode) = &param.mode {
12143                if let Some(text) = &param.mode_text {
12144                    self.write(text);
12145                } else {
12146                    match mode {
12147                        ParameterMode::In => self.write_keyword("IN"),
12148                        ParameterMode::Out => self.write_keyword("OUT"),
12149                        ParameterMode::InOut => self.write_keyword("INOUT"),
12150                        ParameterMode::Variadic => self.write_keyword("VARIADIC"),
12151                    }
12152                }
12153                self.write_space();
12154            }
12155
12156            if let Some(name) = &param.name {
12157                self.generate_identifier(name)?;
12158                // Skip space and type for empty Custom types (e.g., DuckDB macros)
12159                let skip_type =
12160                    matches!(&param.data_type, DataType::Custom { name } if name.is_empty());
12161                if !skip_type {
12162                    self.write_space();
12163                    self.generate_data_type(&param.data_type)?;
12164                }
12165            } else {
12166                self.generate_data_type(&param.data_type)?;
12167            }
12168
12169            if let Some(default) = &param.default {
12170                if self.config.parameter_default_equals {
12171                    self.write(" = ");
12172                } else {
12173                    self.write(" DEFAULT ");
12174                }
12175                self.generate_expression(default)?;
12176            }
12177        }
12178
12179        Ok(())
12180    }
12181
12182    fn generate_drop_function(&mut self, df: &DropFunction) -> Result<()> {
12183        self.write_keyword("DROP FUNCTION");
12184
12185        if df.if_exists {
12186            self.write_space();
12187            self.write_keyword("IF EXISTS");
12188        }
12189
12190        self.write_space();
12191        self.generate_table(&df.name)?;
12192
12193        if let Some(params) = &df.parameters {
12194            self.write(" (");
12195            for (i, dt) in params.iter().enumerate() {
12196                if i > 0 {
12197                    self.write(", ");
12198                }
12199                self.generate_data_type(dt)?;
12200            }
12201            self.write(")");
12202        }
12203
12204        if df.cascade {
12205            self.write_space();
12206            self.write_keyword("CASCADE");
12207        }
12208
12209        Ok(())
12210    }
12211
12212    fn generate_create_procedure(&mut self, cp: &CreateProcedure) -> Result<()> {
12213        self.write_keyword("CREATE");
12214
12215        if cp.or_replace {
12216            self.write_space();
12217            self.write_keyword("OR REPLACE");
12218        }
12219
12220        self.write_space();
12221        if cp.use_proc_keyword {
12222            self.write_keyword("PROC");
12223        } else {
12224            self.write_keyword("PROCEDURE");
12225        }
12226
12227        if cp.if_not_exists {
12228            self.write_space();
12229            self.write_keyword("IF NOT EXISTS");
12230        }
12231
12232        self.write_space();
12233        self.generate_table(&cp.name)?;
12234        if cp.has_parens {
12235            self.write("(");
12236            self.generate_function_parameters(&cp.parameters)?;
12237            self.write(")");
12238        } else if !cp.parameters.is_empty() {
12239            // TSQL: unparenthesized parameters
12240            self.write_space();
12241            self.generate_function_parameters(&cp.parameters)?;
12242        }
12243
12244        // RETURNS clause (Snowflake)
12245        if let Some(return_type) = &cp.return_type {
12246            self.write_space();
12247            self.write_keyword("RETURNS");
12248            self.write_space();
12249            self.generate_data_type(return_type)?;
12250        }
12251
12252        // EXECUTE AS clause (Snowflake)
12253        if let Some(execute_as) = &cp.execute_as {
12254            self.write_space();
12255            self.write_keyword("EXECUTE AS");
12256            self.write_space();
12257            self.write_keyword(execute_as);
12258        }
12259
12260        if let Some(lang) = &cp.language {
12261            self.write_space();
12262            self.write_keyword("LANGUAGE");
12263            self.write_space();
12264            self.write(lang);
12265        }
12266
12267        if let Some(security) = &cp.security {
12268            self.write_space();
12269            self.write_keyword("SECURITY");
12270            self.write_space();
12271            match security {
12272                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
12273                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
12274                FunctionSecurity::None => self.write_keyword("NONE"),
12275            }
12276        }
12277
12278        // TSQL WITH options (ENCRYPTION, RECOMPILE, etc.)
12279        if !cp.with_options.is_empty() {
12280            self.write_space();
12281            self.write_keyword("WITH");
12282            self.write_space();
12283            for (i, opt) in cp.with_options.iter().enumerate() {
12284                if i > 0 {
12285                    self.write(", ");
12286                }
12287                self.write(opt);
12288            }
12289        }
12290
12291        if let Some(body) = &cp.body {
12292            self.write_space();
12293            match body {
12294                FunctionBody::Block(block) => {
12295                    self.write_keyword("AS");
12296                    if matches!(
12297                        self.config.dialect,
12298                        Some(crate::dialects::DialectType::TSQL)
12299                    ) {
12300                        self.write(" BEGIN ");
12301                        self.write(block);
12302                        self.write(" END");
12303                    } else if matches!(
12304                        self.config.dialect,
12305                        Some(crate::dialects::DialectType::PostgreSQL)
12306                    ) {
12307                        self.write(" $$");
12308                        self.write(block);
12309                        self.write("$$");
12310                    } else {
12311                        // Escape content for single-quoted output
12312                        let escaped = self.escape_block_for_single_quote(block);
12313                        self.write(" '");
12314                        self.write(&escaped);
12315                        self.write("'");
12316                    }
12317                }
12318                FunctionBody::StringLiteral(s) => {
12319                    self.write_keyword("AS");
12320                    self.write(" '");
12321                    self.write(s);
12322                    self.write("'");
12323                }
12324                FunctionBody::Expression(expr) => {
12325                    self.write_keyword("AS");
12326                    self.write_space();
12327                    self.generate_expression(expr)?;
12328                }
12329                FunctionBody::External(name) => {
12330                    self.write_keyword("EXTERNAL NAME");
12331                    self.write(" '");
12332                    self.write(name);
12333                    self.write("'");
12334                }
12335                FunctionBody::Return(expr) => {
12336                    self.write_keyword("RETURN");
12337                    self.write_space();
12338                    self.generate_expression(expr)?;
12339                }
12340                FunctionBody::Statements(stmts) => {
12341                    self.write_keyword("AS");
12342                    self.write(" BEGIN ");
12343                    for (i, stmt) in stmts.iter().enumerate() {
12344                        if i > 0 {
12345                            self.write(" ");
12346                        }
12347                        self.generate_expression(stmt)?;
12348                    }
12349                    self.write(" END");
12350                }
12351                FunctionBody::DollarQuoted { content, tag } => {
12352                    self.write_keyword("AS");
12353                    self.write(" ");
12354                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
12355                    let supports_dollar_quoting = matches!(
12356                        self.config.dialect,
12357                        Some(crate::dialects::DialectType::PostgreSQL)
12358                            | Some(crate::dialects::DialectType::Databricks)
12359                            | Some(crate::dialects::DialectType::Redshift)
12360                            | Some(crate::dialects::DialectType::DuckDB)
12361                    );
12362                    if supports_dollar_quoting {
12363                        // Output in dollar-quoted format
12364                        self.write("$");
12365                        if let Some(t) = tag {
12366                            self.write(t);
12367                        }
12368                        self.write("$");
12369                        self.write(content);
12370                        self.write("$");
12371                        if let Some(t) = tag {
12372                            self.write(t);
12373                        }
12374                        self.write("$");
12375                    } else {
12376                        // Convert to single-quoted string for other dialects
12377                        let escaped = self.escape_block_for_single_quote(content);
12378                        self.write("'");
12379                        self.write(&escaped);
12380                        self.write("'");
12381                    }
12382                }
12383            }
12384        }
12385
12386        Ok(())
12387    }
12388
12389    fn generate_drop_procedure(&mut self, dp: &DropProcedure) -> Result<()> {
12390        self.write_keyword("DROP PROCEDURE");
12391
12392        if dp.if_exists {
12393            self.write_space();
12394            self.write_keyword("IF EXISTS");
12395        }
12396
12397        self.write_space();
12398        self.generate_table(&dp.name)?;
12399
12400        if let Some(params) = &dp.parameters {
12401            self.write(" (");
12402            for (i, dt) in params.iter().enumerate() {
12403                if i > 0 {
12404                    self.write(", ");
12405                }
12406                self.generate_data_type(dt)?;
12407            }
12408            self.write(")");
12409        }
12410
12411        if dp.cascade {
12412            self.write_space();
12413            self.write_keyword("CASCADE");
12414        }
12415
12416        Ok(())
12417    }
12418
12419    fn generate_create_sequence(&mut self, cs: &CreateSequence) -> Result<()> {
12420        self.write_keyword("CREATE");
12421
12422        if cs.or_replace {
12423            self.write_space();
12424            self.write_keyword("OR REPLACE");
12425        }
12426
12427        if cs.temporary {
12428            self.write_space();
12429            self.write_keyword("TEMPORARY");
12430        }
12431
12432        self.write_space();
12433        self.write_keyword("SEQUENCE");
12434
12435        if cs.if_not_exists {
12436            self.write_space();
12437            self.write_keyword("IF NOT EXISTS");
12438        }
12439
12440        self.write_space();
12441        self.generate_table(&cs.name)?;
12442
12443        // Output AS <type> if present
12444        if let Some(as_type) = &cs.as_type {
12445            self.write_space();
12446            self.write_keyword("AS");
12447            self.write_space();
12448            self.generate_data_type(as_type)?;
12449        }
12450
12451        // Output COMMENT first (Snowflake convention: COMMENT comes before other properties)
12452        if let Some(comment) = &cs.comment {
12453            self.write_space();
12454            self.write_keyword("COMMENT");
12455            self.write("=");
12456            self.generate_string_literal(comment)?;
12457        }
12458
12459        // If property_order is available, use it to preserve original order
12460        if !cs.property_order.is_empty() {
12461            for prop in &cs.property_order {
12462                match prop {
12463                    SeqPropKind::Start => {
12464                        if let Some(start) = cs.start {
12465                            self.write_space();
12466                            self.write_keyword("START WITH");
12467                            self.write(&format!(" {}", start));
12468                        }
12469                    }
12470                    SeqPropKind::Increment => {
12471                        if let Some(inc) = cs.increment {
12472                            self.write_space();
12473                            self.write_keyword("INCREMENT BY");
12474                            self.write(&format!(" {}", inc));
12475                        }
12476                    }
12477                    SeqPropKind::Minvalue => {
12478                        if let Some(min) = &cs.minvalue {
12479                            self.write_space();
12480                            match min {
12481                                SequenceBound::Value(v) => {
12482                                    self.write_keyword("MINVALUE");
12483                                    self.write(&format!(" {}", v));
12484                                }
12485                                SequenceBound::None => {
12486                                    self.write_keyword("NO MINVALUE");
12487                                }
12488                            }
12489                        }
12490                    }
12491                    SeqPropKind::Maxvalue => {
12492                        if let Some(max) = &cs.maxvalue {
12493                            self.write_space();
12494                            match max {
12495                                SequenceBound::Value(v) => {
12496                                    self.write_keyword("MAXVALUE");
12497                                    self.write(&format!(" {}", v));
12498                                }
12499                                SequenceBound::None => {
12500                                    self.write_keyword("NO MAXVALUE");
12501                                }
12502                            }
12503                        }
12504                    }
12505                    SeqPropKind::Cache => {
12506                        if let Some(cache) = cs.cache {
12507                            self.write_space();
12508                            self.write_keyword("CACHE");
12509                            self.write(&format!(" {}", cache));
12510                        }
12511                    }
12512                    SeqPropKind::NoCache => {
12513                        self.write_space();
12514                        self.write_keyword("NO CACHE");
12515                    }
12516                    SeqPropKind::NoCacheWord => {
12517                        self.write_space();
12518                        self.write_keyword("NOCACHE");
12519                    }
12520                    SeqPropKind::Cycle => {
12521                        self.write_space();
12522                        self.write_keyword("CYCLE");
12523                    }
12524                    SeqPropKind::NoCycle => {
12525                        self.write_space();
12526                        self.write_keyword("NO CYCLE");
12527                    }
12528                    SeqPropKind::NoCycleWord => {
12529                        self.write_space();
12530                        self.write_keyword("NOCYCLE");
12531                    }
12532                    SeqPropKind::OwnedBy => {
12533                        // Skip OWNED BY NONE (it's a no-op)
12534                        if !cs.owned_by_none {
12535                            if let Some(owned) = &cs.owned_by {
12536                                self.write_space();
12537                                self.write_keyword("OWNED BY");
12538                                self.write_space();
12539                                self.generate_table(owned)?;
12540                            }
12541                        }
12542                    }
12543                    SeqPropKind::Order => {
12544                        self.write_space();
12545                        self.write_keyword("ORDER");
12546                    }
12547                    SeqPropKind::NoOrder => {
12548                        self.write_space();
12549                        self.write_keyword("NOORDER");
12550                    }
12551                    SeqPropKind::Comment => {
12552                        // COMMENT is output above, before property_order iteration
12553                    }
12554                    SeqPropKind::Sharing => {
12555                        if let Some(val) = &cs.sharing {
12556                            self.write_space();
12557                            self.write(&format!("SHARING={}", val));
12558                        }
12559                    }
12560                    SeqPropKind::Keep => {
12561                        self.write_space();
12562                        self.write_keyword("KEEP");
12563                    }
12564                    SeqPropKind::NoKeep => {
12565                        self.write_space();
12566                        self.write_keyword("NOKEEP");
12567                    }
12568                    SeqPropKind::Scale => {
12569                        self.write_space();
12570                        self.write_keyword("SCALE");
12571                        if let Some(modifier) = &cs.scale_modifier {
12572                            if !modifier.is_empty() {
12573                                self.write_space();
12574                                self.write_keyword(modifier);
12575                            }
12576                        }
12577                    }
12578                    SeqPropKind::NoScale => {
12579                        self.write_space();
12580                        self.write_keyword("NOSCALE");
12581                    }
12582                    SeqPropKind::Shard => {
12583                        self.write_space();
12584                        self.write_keyword("SHARD");
12585                        if let Some(modifier) = &cs.shard_modifier {
12586                            if !modifier.is_empty() {
12587                                self.write_space();
12588                                self.write_keyword(modifier);
12589                            }
12590                        }
12591                    }
12592                    SeqPropKind::NoShard => {
12593                        self.write_space();
12594                        self.write_keyword("NOSHARD");
12595                    }
12596                    SeqPropKind::Session => {
12597                        self.write_space();
12598                        self.write_keyword("SESSION");
12599                    }
12600                    SeqPropKind::Global => {
12601                        self.write_space();
12602                        self.write_keyword("GLOBAL");
12603                    }
12604                    SeqPropKind::NoMinvalueWord => {
12605                        self.write_space();
12606                        self.write_keyword("NOMINVALUE");
12607                    }
12608                    SeqPropKind::NoMaxvalueWord => {
12609                        self.write_space();
12610                        self.write_keyword("NOMAXVALUE");
12611                    }
12612                }
12613            }
12614        } else {
12615            // Fallback: default order for backwards compatibility
12616            if let Some(inc) = cs.increment {
12617                self.write_space();
12618                self.write_keyword("INCREMENT BY");
12619                self.write(&format!(" {}", inc));
12620            }
12621
12622            if let Some(min) = &cs.minvalue {
12623                self.write_space();
12624                match min {
12625                    SequenceBound::Value(v) => {
12626                        self.write_keyword("MINVALUE");
12627                        self.write(&format!(" {}", v));
12628                    }
12629                    SequenceBound::None => {
12630                        self.write_keyword("NO MINVALUE");
12631                    }
12632                }
12633            }
12634
12635            if let Some(max) = &cs.maxvalue {
12636                self.write_space();
12637                match max {
12638                    SequenceBound::Value(v) => {
12639                        self.write_keyword("MAXVALUE");
12640                        self.write(&format!(" {}", v));
12641                    }
12642                    SequenceBound::None => {
12643                        self.write_keyword("NO MAXVALUE");
12644                    }
12645                }
12646            }
12647
12648            if let Some(start) = cs.start {
12649                self.write_space();
12650                self.write_keyword("START WITH");
12651                self.write(&format!(" {}", start));
12652            }
12653
12654            if let Some(cache) = cs.cache {
12655                self.write_space();
12656                self.write_keyword("CACHE");
12657                self.write(&format!(" {}", cache));
12658            }
12659
12660            if cs.cycle {
12661                self.write_space();
12662                self.write_keyword("CYCLE");
12663            }
12664
12665            if let Some(owned) = &cs.owned_by {
12666                self.write_space();
12667                self.write_keyword("OWNED BY");
12668                self.write_space();
12669                self.generate_table(owned)?;
12670            }
12671        }
12672
12673        Ok(())
12674    }
12675
12676    fn generate_drop_sequence(&mut self, ds: &DropSequence) -> Result<()> {
12677        self.write_keyword("DROP SEQUENCE");
12678
12679        if ds.if_exists {
12680            self.write_space();
12681            self.write_keyword("IF EXISTS");
12682        }
12683
12684        self.write_space();
12685        self.generate_table(&ds.name)?;
12686
12687        if ds.cascade {
12688            self.write_space();
12689            self.write_keyword("CASCADE");
12690        }
12691
12692        Ok(())
12693    }
12694
12695    fn generate_alter_sequence(&mut self, als: &AlterSequence) -> Result<()> {
12696        self.write_keyword("ALTER SEQUENCE");
12697
12698        if als.if_exists {
12699            self.write_space();
12700            self.write_keyword("IF EXISTS");
12701        }
12702
12703        self.write_space();
12704        self.generate_table(&als.name)?;
12705
12706        if let Some(inc) = als.increment {
12707            self.write_space();
12708            self.write_keyword("INCREMENT BY");
12709            self.write(&format!(" {}", inc));
12710        }
12711
12712        if let Some(min) = &als.minvalue {
12713            self.write_space();
12714            match min {
12715                SequenceBound::Value(v) => {
12716                    self.write_keyword("MINVALUE");
12717                    self.write(&format!(" {}", v));
12718                }
12719                SequenceBound::None => {
12720                    self.write_keyword("NO MINVALUE");
12721                }
12722            }
12723        }
12724
12725        if let Some(max) = &als.maxvalue {
12726            self.write_space();
12727            match max {
12728                SequenceBound::Value(v) => {
12729                    self.write_keyword("MAXVALUE");
12730                    self.write(&format!(" {}", v));
12731                }
12732                SequenceBound::None => {
12733                    self.write_keyword("NO MAXVALUE");
12734                }
12735            }
12736        }
12737
12738        if let Some(start) = als.start {
12739            self.write_space();
12740            self.write_keyword("START WITH");
12741            self.write(&format!(" {}", start));
12742        }
12743
12744        if let Some(restart) = &als.restart {
12745            self.write_space();
12746            self.write_keyword("RESTART");
12747            if let Some(val) = restart {
12748                self.write_keyword(" WITH");
12749                self.write(&format!(" {}", val));
12750            }
12751        }
12752
12753        if let Some(cache) = als.cache {
12754            self.write_space();
12755            self.write_keyword("CACHE");
12756            self.write(&format!(" {}", cache));
12757        }
12758
12759        if let Some(cycle) = als.cycle {
12760            self.write_space();
12761            if cycle {
12762                self.write_keyword("CYCLE");
12763            } else {
12764                self.write_keyword("NO CYCLE");
12765            }
12766        }
12767
12768        if let Some(owned) = &als.owned_by {
12769            self.write_space();
12770            self.write_keyword("OWNED BY");
12771            self.write_space();
12772            if let Some(table) = owned {
12773                self.generate_table(table)?;
12774            } else {
12775                self.write_keyword("NONE");
12776            }
12777        }
12778
12779        Ok(())
12780    }
12781
12782    fn generate_create_trigger(&mut self, ct: &CreateTrigger) -> Result<()> {
12783        self.write_keyword("CREATE");
12784
12785        if ct.or_replace {
12786            self.write_space();
12787            self.write_keyword("OR REPLACE");
12788        }
12789
12790        if ct.constraint {
12791            self.write_space();
12792            self.write_keyword("CONSTRAINT");
12793        }
12794
12795        self.write_space();
12796        self.write_keyword("TRIGGER");
12797        self.write_space();
12798        self.generate_identifier(&ct.name)?;
12799
12800        self.write_space();
12801        match ct.timing {
12802            TriggerTiming::Before => self.write_keyword("BEFORE"),
12803            TriggerTiming::After => self.write_keyword("AFTER"),
12804            TriggerTiming::InsteadOf => self.write_keyword("INSTEAD OF"),
12805        }
12806
12807        // Events
12808        for (i, event) in ct.events.iter().enumerate() {
12809            if i > 0 {
12810                self.write_keyword(" OR");
12811            }
12812            self.write_space();
12813            match event {
12814                TriggerEvent::Insert => self.write_keyword("INSERT"),
12815                TriggerEvent::Update(cols) => {
12816                    self.write_keyword("UPDATE");
12817                    if let Some(cols) = cols {
12818                        self.write_space();
12819                        self.write_keyword("OF");
12820                        for (j, col) in cols.iter().enumerate() {
12821                            if j > 0 {
12822                                self.write(",");
12823                            }
12824                            self.write_space();
12825                            self.generate_identifier(col)?;
12826                        }
12827                    }
12828                }
12829                TriggerEvent::Delete => self.write_keyword("DELETE"),
12830                TriggerEvent::Truncate => self.write_keyword("TRUNCATE"),
12831            }
12832        }
12833
12834        self.write_space();
12835        self.write_keyword("ON");
12836        self.write_space();
12837        self.generate_table(&ct.table)?;
12838
12839        // Referencing clause
12840        if let Some(ref_clause) = &ct.referencing {
12841            self.write_space();
12842            self.write_keyword("REFERENCING");
12843            if let Some(old_table) = &ref_clause.old_table {
12844                self.write_space();
12845                self.write_keyword("OLD TABLE AS");
12846                self.write_space();
12847                self.generate_identifier(old_table)?;
12848            }
12849            if let Some(new_table) = &ref_clause.new_table {
12850                self.write_space();
12851                self.write_keyword("NEW TABLE AS");
12852                self.write_space();
12853                self.generate_identifier(new_table)?;
12854            }
12855            if let Some(old_row) = &ref_clause.old_row {
12856                self.write_space();
12857                self.write_keyword("OLD ROW AS");
12858                self.write_space();
12859                self.generate_identifier(old_row)?;
12860            }
12861            if let Some(new_row) = &ref_clause.new_row {
12862                self.write_space();
12863                self.write_keyword("NEW ROW AS");
12864                self.write_space();
12865                self.generate_identifier(new_row)?;
12866            }
12867        }
12868
12869        // Deferrable options for constraint triggers (must come before FOR EACH)
12870        if let Some(deferrable) = ct.deferrable {
12871            self.write_space();
12872            if deferrable {
12873                self.write_keyword("DEFERRABLE");
12874            } else {
12875                self.write_keyword("NOT DEFERRABLE");
12876            }
12877        }
12878
12879        if let Some(initially) = ct.initially_deferred {
12880            self.write_space();
12881            self.write_keyword("INITIALLY");
12882            self.write_space();
12883            if initially {
12884                self.write_keyword("DEFERRED");
12885            } else {
12886                self.write_keyword("IMMEDIATE");
12887            }
12888        }
12889
12890        self.write_space();
12891        self.write_keyword("FOR EACH");
12892        self.write_space();
12893        match ct.for_each {
12894            TriggerForEach::Row => self.write_keyword("ROW"),
12895            TriggerForEach::Statement => self.write_keyword("STATEMENT"),
12896        }
12897
12898        // When clause
12899        if let Some(when) = &ct.when {
12900            self.write_space();
12901            self.write_keyword("WHEN");
12902            self.write(" (");
12903            self.generate_expression(when)?;
12904            self.write(")");
12905        }
12906
12907        // Body
12908        self.write_space();
12909        match &ct.body {
12910            TriggerBody::Execute { function, args } => {
12911                self.write_keyword("EXECUTE FUNCTION");
12912                self.write_space();
12913                self.generate_table(function)?;
12914                self.write("(");
12915                for (i, arg) in args.iter().enumerate() {
12916                    if i > 0 {
12917                        self.write(", ");
12918                    }
12919                    self.generate_expression(arg)?;
12920                }
12921                self.write(")");
12922            }
12923            TriggerBody::Block(block) => {
12924                self.write_keyword("BEGIN");
12925                self.write_space();
12926                self.write(block);
12927                self.write_space();
12928                self.write_keyword("END");
12929            }
12930        }
12931
12932        Ok(())
12933    }
12934
12935    fn generate_drop_trigger(&mut self, dt: &DropTrigger) -> Result<()> {
12936        self.write_keyword("DROP TRIGGER");
12937
12938        if dt.if_exists {
12939            self.write_space();
12940            self.write_keyword("IF EXISTS");
12941        }
12942
12943        self.write_space();
12944        self.generate_identifier(&dt.name)?;
12945
12946        if let Some(table) = &dt.table {
12947            self.write_space();
12948            self.write_keyword("ON");
12949            self.write_space();
12950            self.generate_table(table)?;
12951        }
12952
12953        if dt.cascade {
12954            self.write_space();
12955            self.write_keyword("CASCADE");
12956        }
12957
12958        Ok(())
12959    }
12960
12961    fn generate_create_type(&mut self, ct: &CreateType) -> Result<()> {
12962        self.write_keyword("CREATE TYPE");
12963
12964        if ct.if_not_exists {
12965            self.write_space();
12966            self.write_keyword("IF NOT EXISTS");
12967        }
12968
12969        self.write_space();
12970        self.generate_table(&ct.name)?;
12971
12972        self.write_space();
12973        self.write_keyword("AS");
12974        self.write_space();
12975
12976        match &ct.definition {
12977            TypeDefinition::Enum(values) => {
12978                self.write_keyword("ENUM");
12979                self.write(" (");
12980                for (i, val) in values.iter().enumerate() {
12981                    if i > 0 {
12982                        self.write(", ");
12983                    }
12984                    self.write(&format!("'{}'", val));
12985                }
12986                self.write(")");
12987            }
12988            TypeDefinition::Composite(attrs) => {
12989                self.write("(");
12990                for (i, attr) in attrs.iter().enumerate() {
12991                    if i > 0 {
12992                        self.write(", ");
12993                    }
12994                    self.generate_identifier(&attr.name)?;
12995                    self.write_space();
12996                    self.generate_data_type(&attr.data_type)?;
12997                    if let Some(collate) = &attr.collate {
12998                        self.write_space();
12999                        self.write_keyword("COLLATE");
13000                        self.write_space();
13001                        self.generate_identifier(collate)?;
13002                    }
13003                }
13004                self.write(")");
13005            }
13006            TypeDefinition::Range {
13007                subtype,
13008                subtype_diff,
13009                canonical,
13010            } => {
13011                self.write_keyword("RANGE");
13012                self.write(" (");
13013                self.write_keyword("SUBTYPE");
13014                self.write(" = ");
13015                self.generate_data_type(subtype)?;
13016                if let Some(diff) = subtype_diff {
13017                    self.write(", ");
13018                    self.write_keyword("SUBTYPE_DIFF");
13019                    self.write(" = ");
13020                    self.write(diff);
13021                }
13022                if let Some(canon) = canonical {
13023                    self.write(", ");
13024                    self.write_keyword("CANONICAL");
13025                    self.write(" = ");
13026                    self.write(canon);
13027                }
13028                self.write(")");
13029            }
13030            TypeDefinition::Base {
13031                input,
13032                output,
13033                internallength,
13034            } => {
13035                self.write("(");
13036                self.write_keyword("INPUT");
13037                self.write(" = ");
13038                self.write(input);
13039                self.write(", ");
13040                self.write_keyword("OUTPUT");
13041                self.write(" = ");
13042                self.write(output);
13043                if let Some(len) = internallength {
13044                    self.write(", ");
13045                    self.write_keyword("INTERNALLENGTH");
13046                    self.write(" = ");
13047                    self.write(&len.to_string());
13048                }
13049                self.write(")");
13050            }
13051            TypeDefinition::Domain {
13052                base_type,
13053                default,
13054                constraints,
13055            } => {
13056                self.generate_data_type(base_type)?;
13057                if let Some(def) = default {
13058                    self.write_space();
13059                    self.write_keyword("DEFAULT");
13060                    self.write_space();
13061                    self.generate_expression(def)?;
13062                }
13063                for constr in constraints {
13064                    self.write_space();
13065                    if let Some(name) = &constr.name {
13066                        self.write_keyword("CONSTRAINT");
13067                        self.write_space();
13068                        self.generate_identifier(name)?;
13069                        self.write_space();
13070                    }
13071                    self.write_keyword("CHECK");
13072                    self.write(" (");
13073                    self.generate_expression(&constr.check)?;
13074                    self.write(")");
13075                }
13076            }
13077        }
13078
13079        Ok(())
13080    }
13081
13082    fn generate_drop_type(&mut self, dt: &DropType) -> Result<()> {
13083        self.write_keyword("DROP TYPE");
13084
13085        if dt.if_exists {
13086            self.write_space();
13087            self.write_keyword("IF EXISTS");
13088        }
13089
13090        self.write_space();
13091        self.generate_table(&dt.name)?;
13092
13093        if dt.cascade {
13094            self.write_space();
13095            self.write_keyword("CASCADE");
13096        }
13097
13098        Ok(())
13099    }
13100
13101    fn generate_describe(&mut self, d: &Describe) -> Result<()> {
13102        // Athena: DESCRIBE uses Hive engine (backticks)
13103        let saved_athena_hive_context = self.athena_hive_context;
13104        if matches!(
13105            self.config.dialect,
13106            Some(crate::dialects::DialectType::Athena)
13107        ) {
13108            self.athena_hive_context = true;
13109        }
13110
13111        // Output leading comments before DESCRIBE
13112        for comment in &d.leading_comments {
13113            self.write_formatted_comment(comment);
13114            self.write(" ");
13115        }
13116
13117        self.write_keyword("DESCRIBE");
13118
13119        if d.extended {
13120            self.write_space();
13121            self.write_keyword("EXTENDED");
13122        } else if d.formatted {
13123            self.write_space();
13124            self.write_keyword("FORMATTED");
13125        }
13126
13127        // Output style like ANALYZE, HISTORY
13128        if let Some(ref style) = d.style {
13129            self.write_space();
13130            self.write_keyword(style);
13131        }
13132
13133        // Handle object kind (TABLE, VIEW) based on dialect
13134        let should_output_kind = match self.config.dialect {
13135            // Spark doesn't use TABLE/VIEW after DESCRIBE
13136            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
13137                false
13138            }
13139            // Snowflake always includes TABLE
13140            Some(DialectType::Snowflake) => true,
13141            _ => d.kind.is_some(),
13142        };
13143        if should_output_kind {
13144            if let Some(ref kind) = d.kind {
13145                self.write_space();
13146                self.write_keyword(kind);
13147            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
13148                self.write_space();
13149                self.write_keyword("TABLE");
13150            }
13151        }
13152
13153        self.write_space();
13154        self.generate_expression(&d.target)?;
13155
13156        // Output PARTITION clause if present (the Partition expression outputs its own PARTITION keyword)
13157        if let Some(ref partition) = d.partition {
13158            self.write_space();
13159            self.generate_expression(partition)?;
13160        }
13161
13162        // Databricks: AS JSON
13163        if d.as_json {
13164            self.write_space();
13165            self.write_keyword("AS JSON");
13166        }
13167
13168        // Output properties like type=stage
13169        for (name, value) in &d.properties {
13170            self.write_space();
13171            self.write(name);
13172            self.write("=");
13173            self.write(value);
13174        }
13175
13176        // Restore Athena Hive context
13177        self.athena_hive_context = saved_athena_hive_context;
13178
13179        Ok(())
13180    }
13181
13182    /// Generate SHOW statement (Snowflake, MySQL, etc.)
13183    /// SHOW [TERSE] <object_type> [HISTORY] [LIKE pattern] [IN <scope>] [STARTS WITH pattern] [LIMIT n] [FROM object]
13184    fn generate_show(&mut self, s: &Show) -> Result<()> {
13185        self.write_keyword("SHOW");
13186        self.write_space();
13187
13188        // TERSE keyword - but not for PRIMARY KEYS, UNIQUE KEYS, IMPORTED KEYS
13189        // where TERSE is syntactically valid but has no effect on output
13190        let show_terse = s.terse
13191            && !matches!(
13192                s.this.as_str(),
13193                "PRIMARY KEYS" | "UNIQUE KEYS" | "IMPORTED KEYS"
13194            );
13195        if show_terse {
13196            self.write_keyword("TERSE");
13197            self.write_space();
13198        }
13199
13200        // Object type (USERS, TABLES, DATABASES, etc.)
13201        self.write_keyword(&s.this);
13202
13203        // Target identifier (MySQL: engine name in SHOW ENGINE, preserved case)
13204        if let Some(ref target_expr) = s.target {
13205            self.write_space();
13206            self.generate_expression(target_expr)?;
13207        }
13208
13209        // HISTORY keyword
13210        if s.history {
13211            self.write_space();
13212            self.write_keyword("HISTORY");
13213        }
13214
13215        // FOR target (MySQL: SHOW GRANTS FOR foo, SHOW PROFILE ... FOR QUERY 5)
13216        if let Some(ref for_target) = s.for_target {
13217            self.write_space();
13218            self.write_keyword("FOR");
13219            self.write_space();
13220            self.generate_expression(for_target)?;
13221        }
13222
13223        // Determine ordering based on dialect:
13224        // - Snowflake: LIKE, IN, STARTS WITH, LIMIT, FROM
13225        // - MySQL: IN, FROM, LIKE (when FROM is present)
13226        use crate::dialects::DialectType;
13227        let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
13228
13229        if !is_snowflake && s.from.is_some() {
13230            // MySQL ordering: IN, FROM, LIKE
13231
13232            // IN scope_kind [scope]
13233            if let Some(ref scope_kind) = s.scope_kind {
13234                self.write_space();
13235                self.write_keyword("IN");
13236                self.write_space();
13237                self.write_keyword(scope_kind);
13238                if let Some(ref scope) = s.scope {
13239                    self.write_space();
13240                    self.generate_expression(scope)?;
13241                }
13242            } else if let Some(ref scope) = s.scope {
13243                self.write_space();
13244                self.write_keyword("IN");
13245                self.write_space();
13246                self.generate_expression(scope)?;
13247            }
13248
13249            // FROM clause
13250            if let Some(ref from) = s.from {
13251                self.write_space();
13252                self.write_keyword("FROM");
13253                self.write_space();
13254                self.generate_expression(from)?;
13255            }
13256
13257            // Second FROM clause (db name)
13258            if let Some(ref db) = s.db {
13259                self.write_space();
13260                self.write_keyword("FROM");
13261                self.write_space();
13262                self.generate_expression(db)?;
13263            }
13264
13265            // LIKE pattern
13266            if let Some(ref like) = s.like {
13267                self.write_space();
13268                self.write_keyword("LIKE");
13269                self.write_space();
13270                self.generate_expression(like)?;
13271            }
13272        } else {
13273            // Snowflake ordering: LIKE, IN, STARTS WITH, LIMIT, FROM
13274
13275            // LIKE pattern
13276            if let Some(ref like) = s.like {
13277                self.write_space();
13278                self.write_keyword("LIKE");
13279                self.write_space();
13280                self.generate_expression(like)?;
13281            }
13282
13283            // IN scope_kind [scope]
13284            if let Some(ref scope_kind) = s.scope_kind {
13285                self.write_space();
13286                self.write_keyword("IN");
13287                self.write_space();
13288                self.write_keyword(scope_kind);
13289                if let Some(ref scope) = s.scope {
13290                    self.write_space();
13291                    self.generate_expression(scope)?;
13292                }
13293            } else if let Some(ref scope) = s.scope {
13294                self.write_space();
13295                self.write_keyword("IN");
13296                self.write_space();
13297                self.generate_expression(scope)?;
13298            }
13299        }
13300
13301        // STARTS WITH pattern
13302        if let Some(ref starts_with) = s.starts_with {
13303            self.write_space();
13304            self.write_keyword("STARTS WITH");
13305            self.write_space();
13306            self.generate_expression(starts_with)?;
13307        }
13308
13309        // LIMIT clause
13310        if let Some(ref limit) = s.limit {
13311            self.write_space();
13312            self.generate_limit(limit)?;
13313        }
13314
13315        // FROM clause (for Snowflake, FROM comes after STARTS WITH and LIMIT)
13316        if is_snowflake {
13317            if let Some(ref from) = s.from {
13318                self.write_space();
13319                self.write_keyword("FROM");
13320                self.write_space();
13321                self.generate_expression(from)?;
13322            }
13323        }
13324
13325        // WHERE clause (MySQL: SHOW STATUS WHERE condition)
13326        if let Some(ref where_clause) = s.where_clause {
13327            self.write_space();
13328            self.write_keyword("WHERE");
13329            self.write_space();
13330            self.generate_expression(where_clause)?;
13331        }
13332
13333        // MUTEX/STATUS suffix (MySQL: SHOW ENGINE foo STATUS/MUTEX)
13334        if let Some(is_mutex) = s.mutex {
13335            self.write_space();
13336            if is_mutex {
13337                self.write_keyword("MUTEX");
13338            } else {
13339                self.write_keyword("STATUS");
13340            }
13341        }
13342
13343        // WITH PRIVILEGES clause (Snowflake: SHOW ... WITH PRIVILEGES USAGE, MODIFY)
13344        if !s.privileges.is_empty() {
13345            self.write_space();
13346            self.write_keyword("WITH PRIVILEGES");
13347            self.write_space();
13348            for (i, priv_name) in s.privileges.iter().enumerate() {
13349                if i > 0 {
13350                    self.write(", ");
13351                }
13352                self.write_keyword(priv_name);
13353            }
13354        }
13355
13356        Ok(())
13357    }
13358
13359    // ==================== End DDL Generation ====================
13360
13361    fn generate_literal(&mut self, lit: &Literal) -> Result<()> {
13362        use crate::dialects::DialectType;
13363        match lit {
13364            Literal::String(s) => {
13365                self.generate_string_literal(s)?;
13366            }
13367            Literal::Number(n) => {
13368                if matches!(self.config.dialect, Some(DialectType::MySQL))
13369                    && n.len() > 2
13370                    && (n.starts_with("0x") || n.starts_with("0X"))
13371                    && !n[2..].chars().all(|c| c.is_ascii_hexdigit())
13372                {
13373                    return self.generate_identifier(&Identifier {
13374                        name: n.clone(),
13375                        quoted: true,
13376                        trailing_comments: Vec::new(),
13377                    });
13378                }
13379                // Strip underscore digit separators (e.g., 1_000_000 -> 1000000)
13380                // for dialects that don't support them (MySQL interprets as identifier).
13381                // ClickHouse, DuckDB, PostgreSQL, and Hive/Spark/Databricks support them.
13382                let n = if n.contains('_')
13383                    && !matches!(
13384                        self.config.dialect,
13385                        Some(DialectType::ClickHouse)
13386                            | Some(DialectType::DuckDB)
13387                            | Some(DialectType::PostgreSQL)
13388                            | Some(DialectType::Hive)
13389                            | Some(DialectType::Spark)
13390                            | Some(DialectType::Databricks)
13391                    ) {
13392                    std::borrow::Cow::Owned(n.replace('_', ""))
13393                } else {
13394                    std::borrow::Cow::Borrowed(n.as_str())
13395                };
13396                // Normalize numbers starting with decimal point to have leading zero
13397                // e.g., .25 -> 0.25 (matches sqlglot behavior)
13398                if n.starts_with('.') {
13399                    self.write("0");
13400                    self.write(&n);
13401                } else if n.starts_with("-.") {
13402                    // Handle negative numbers like -.25 -> -0.25
13403                    self.write("-0");
13404                    self.write(&n[1..]);
13405                } else {
13406                    self.write(&n);
13407                }
13408            }
13409            Literal::HexString(h) => {
13410                // Most dialects use lowercase x'...' for hex literals; Spark/Databricks/Teradata use uppercase X'...'
13411                match self.config.dialect {
13412                    Some(DialectType::Spark)
13413                    | Some(DialectType::Databricks)
13414                    | Some(DialectType::Teradata) => self.write("X'"),
13415                    _ => self.write("x'"),
13416                }
13417                self.write(h);
13418                self.write("'");
13419            }
13420            Literal::HexNumber(h) => {
13421                // Hex number (0xA) - integer in hex notation (from BigQuery)
13422                // For BigQuery, TSQL, Fabric output as 0xHEX (native hex notation)
13423                // For other dialects, convert to decimal integer
13424                match self.config.dialect {
13425                    Some(DialectType::BigQuery)
13426                    | Some(DialectType::TSQL)
13427                    | Some(DialectType::Fabric) => {
13428                        self.write("0x");
13429                        self.write(h);
13430                    }
13431                    _ => {
13432                        // Convert hex to decimal
13433                        if let Ok(val) = u64::from_str_radix(h, 16) {
13434                            self.write(&val.to_string());
13435                        } else {
13436                            // Fallback: keep as 0x notation
13437                            self.write("0x");
13438                            self.write(h);
13439                        }
13440                    }
13441                }
13442            }
13443            Literal::BitString(b) => {
13444                // Bit string B'0101...'
13445                self.write("B'");
13446                self.write(b);
13447                self.write("'");
13448            }
13449            Literal::ByteString(b) => {
13450                // Byte string b'...' (BigQuery style)
13451                self.write("b'");
13452                // Escape special characters for output
13453                self.write_escaped_byte_string(b);
13454                self.write("'");
13455            }
13456            Literal::NationalString(s) => {
13457                // N'string' is supported by TSQL, Oracle, MySQL, and generic SQL
13458                // Other dialects strip the N prefix and output as regular string
13459                let keep_n_prefix = matches!(
13460                    self.config.dialect,
13461                    Some(DialectType::TSQL)
13462                        | Some(DialectType::Oracle)
13463                        | Some(DialectType::MySQL)
13464                        | None
13465                );
13466                if keep_n_prefix {
13467                    self.write("N'");
13468                } else {
13469                    self.write("'");
13470                }
13471                self.write(s);
13472                self.write("'");
13473            }
13474            Literal::Date(d) => {
13475                self.generate_date_literal(d)?;
13476            }
13477            Literal::Time(t) => {
13478                self.generate_time_literal(t)?;
13479            }
13480            Literal::Timestamp(ts) => {
13481                self.generate_timestamp_literal(ts)?;
13482            }
13483            Literal::Datetime(dt) => {
13484                self.generate_datetime_literal(dt)?;
13485            }
13486            Literal::TripleQuotedString(s, _quote_char) => {
13487                // For BigQuery and other dialects that don't support triple-quote, normalize to regular strings
13488                if matches!(
13489                    self.config.dialect,
13490                    Some(crate::dialects::DialectType::BigQuery)
13491                        | Some(crate::dialects::DialectType::DuckDB)
13492                        | Some(crate::dialects::DialectType::Snowflake)
13493                        | Some(crate::dialects::DialectType::Spark)
13494                        | Some(crate::dialects::DialectType::Hive)
13495                        | Some(crate::dialects::DialectType::Presto)
13496                        | Some(crate::dialects::DialectType::Trino)
13497                        | Some(crate::dialects::DialectType::PostgreSQL)
13498                        | Some(crate::dialects::DialectType::MySQL)
13499                        | Some(crate::dialects::DialectType::Redshift)
13500                        | Some(crate::dialects::DialectType::TSQL)
13501                        | Some(crate::dialects::DialectType::Oracle)
13502                        | Some(crate::dialects::DialectType::ClickHouse)
13503                        | Some(crate::dialects::DialectType::Databricks)
13504                        | Some(crate::dialects::DialectType::SQLite)
13505                ) {
13506                    self.generate_string_literal(s)?;
13507                } else {
13508                    // Preserve triple-quoted string syntax for generic/unknown dialects
13509                    let quotes = format!("{0}{0}{0}", _quote_char);
13510                    self.write(&quotes);
13511                    self.write(s);
13512                    self.write(&quotes);
13513                }
13514            }
13515            Literal::EscapeString(s) => {
13516                // PostgreSQL escape string: e'...' or E'...'
13517                // Token text format is "e:content" or "E:content"
13518                // Normalize escape sequences: \' -> '' (standard SQL doubled quote)
13519                use crate::dialects::DialectType;
13520                let content = if let Some(c) = s.strip_prefix("e:") {
13521                    c
13522                } else if let Some(c) = s.strip_prefix("E:") {
13523                    c
13524                } else {
13525                    s.as_str()
13526                };
13527
13528                // MySQL: output the content without quotes or prefix
13529                if matches!(
13530                    self.config.dialect,
13531                    Some(DialectType::MySQL) | Some(DialectType::TiDB)
13532                ) {
13533                    self.write(content);
13534                } else {
13535                    // Some dialects use lowercase e' prefix
13536                    let prefix = if matches!(
13537                        self.config.dialect,
13538                        Some(DialectType::SingleStore)
13539                            | Some(DialectType::DuckDB)
13540                            | Some(DialectType::PostgreSQL)
13541                            | Some(DialectType::CockroachDB)
13542                            | Some(DialectType::Materialize)
13543                            | Some(DialectType::RisingWave)
13544                    ) {
13545                        "e'"
13546                    } else {
13547                        "E'"
13548                    };
13549
13550                    // Normalize \' to '' for output
13551                    let normalized = content.replace("\\'", "''");
13552                    self.write(prefix);
13553                    self.write(&normalized);
13554                    self.write("'");
13555                }
13556            }
13557            Literal::DollarString(s) => {
13558                // Convert dollar-quoted strings to single-quoted strings
13559                // (like Python sqlglot's rawstring_sql)
13560                use crate::dialects::DialectType;
13561                // Extract content from tag\x00content format
13562                let (_tag, content) = crate::tokens::parse_dollar_string_token(s);
13563                // Step 1: Escape backslashes if the dialect uses backslash as a string escape
13564                let escape_backslash = matches!(self.config.dialect, Some(DialectType::Snowflake));
13565                // Step 2: Determine quote escaping style
13566                // Snowflake: ' -> \' (backslash escape)
13567                // PostgreSQL, DuckDB, others: ' -> '' (doubled quote)
13568                let use_backslash_quote =
13569                    matches!(self.config.dialect, Some(DialectType::Snowflake));
13570
13571                let mut escaped = String::with_capacity(content.len() + 4);
13572                for ch in content.chars() {
13573                    if escape_backslash && ch == '\\' {
13574                        // Escape backslash first (before quote escaping)
13575                        escaped.push('\\');
13576                        escaped.push('\\');
13577                    } else if ch == '\'' {
13578                        if use_backslash_quote {
13579                            escaped.push('\\');
13580                            escaped.push('\'');
13581                        } else {
13582                            escaped.push('\'');
13583                            escaped.push('\'');
13584                        }
13585                    } else {
13586                        escaped.push(ch);
13587                    }
13588                }
13589                self.write("'");
13590                self.write(&escaped);
13591                self.write("'");
13592            }
13593            Literal::RawString(s) => {
13594                // Raw strings (r"..." or r'...') contain literal backslashes.
13595                // When converting to a regular string, this follows Python sqlglot's rawstring_sql:
13596                // 1. If \\ is in STRING_ESCAPES, double all backslashes
13597                // 2. Apply ESCAPED_SEQUENCES for special chars (but NOT for backslash itself)
13598                // 3. Escape quotes using STRING_ESCAPES[0] + quote_char
13599                use crate::dialects::DialectType;
13600
13601                // Dialects where \\ is in STRING_ESCAPES (backslashes need doubling)
13602                let escape_backslash = matches!(
13603                    self.config.dialect,
13604                    Some(DialectType::BigQuery)
13605                        | Some(DialectType::MySQL)
13606                        | Some(DialectType::SingleStore)
13607                        | Some(DialectType::TiDB)
13608                        | Some(DialectType::Hive)
13609                        | Some(DialectType::Spark)
13610                        | Some(DialectType::Databricks)
13611                        | Some(DialectType::Drill)
13612                        | Some(DialectType::Snowflake)
13613                        | Some(DialectType::Redshift)
13614                        | Some(DialectType::ClickHouse)
13615                );
13616
13617                // Dialects where backslash is the PRIMARY string escape (STRING_ESCAPES[0] = "\\")
13618                // These escape quotes as \' instead of ''
13619                let backslash_escapes_quote = matches!(
13620                    self.config.dialect,
13621                    Some(DialectType::BigQuery)
13622                        | Some(DialectType::Hive)
13623                        | Some(DialectType::Spark)
13624                        | Some(DialectType::Databricks)
13625                        | Some(DialectType::Drill)
13626                        | Some(DialectType::Snowflake)
13627                        | Some(DialectType::Redshift)
13628                );
13629
13630                // Whether this dialect supports escaped sequences (ESCAPED_SEQUENCES mapping)
13631                // This is True when \\ is in STRING_ESCAPES (same as escape_backslash)
13632                let supports_escape_sequences = escape_backslash;
13633
13634                let mut escaped = String::with_capacity(s.len() + 4);
13635                for ch in s.chars() {
13636                    if escape_backslash && ch == '\\' {
13637                        // Double the backslash for the target dialect
13638                        escaped.push('\\');
13639                        escaped.push('\\');
13640                    } else if ch == '\'' {
13641                        if backslash_escapes_quote {
13642                            // Use backslash to escape the quote: \'
13643                            escaped.push('\\');
13644                            escaped.push('\'');
13645                        } else {
13646                            // Use SQL standard quote doubling: ''
13647                            escaped.push('\'');
13648                            escaped.push('\'');
13649                        }
13650                    } else if supports_escape_sequences {
13651                        // Apply ESCAPED_SEQUENCES mapping for special chars
13652                        // (escape_backslash=False in rawstring_sql, so \\ is NOT escaped here)
13653                        match ch {
13654                            '\n' => {
13655                                escaped.push('\\');
13656                                escaped.push('n');
13657                            }
13658                            '\r' => {
13659                                escaped.push('\\');
13660                                escaped.push('r');
13661                            }
13662                            '\t' => {
13663                                escaped.push('\\');
13664                                escaped.push('t');
13665                            }
13666                            '\x07' => {
13667                                escaped.push('\\');
13668                                escaped.push('a');
13669                            }
13670                            '\x08' => {
13671                                escaped.push('\\');
13672                                escaped.push('b');
13673                            }
13674                            '\x0C' => {
13675                                escaped.push('\\');
13676                                escaped.push('f');
13677                            }
13678                            '\x0B' => {
13679                                escaped.push('\\');
13680                                escaped.push('v');
13681                            }
13682                            _ => escaped.push(ch),
13683                        }
13684                    } else {
13685                        escaped.push(ch);
13686                    }
13687                }
13688                self.write("'");
13689                self.write(&escaped);
13690                self.write("'");
13691            }
13692        }
13693        Ok(())
13694    }
13695
13696    /// Generate a DATE literal with dialect-specific formatting
13697    fn generate_date_literal(&mut self, d: &str) -> Result<()> {
13698        use crate::dialects::DialectType;
13699
13700        match self.config.dialect {
13701            // SQL Server uses CONVERT or CAST
13702            Some(DialectType::TSQL) => {
13703                self.write("CAST('");
13704                self.write(d);
13705                self.write("' AS DATE)");
13706            }
13707            // BigQuery uses CAST syntax for type literals
13708            // DATE 'value' -> CAST('value' AS DATE)
13709            Some(DialectType::BigQuery) => {
13710                self.write("CAST('");
13711                self.write(d);
13712                self.write("' AS DATE)");
13713            }
13714            // Exasol uses CAST syntax for DATE literals
13715            // DATE 'value' -> CAST('value' AS DATE)
13716            Some(DialectType::Exasol) => {
13717                self.write("CAST('");
13718                self.write(d);
13719                self.write("' AS DATE)");
13720            }
13721            // Snowflake uses CAST syntax for DATE literals
13722            // DATE 'value' -> CAST('value' AS DATE)
13723            Some(DialectType::Snowflake) => {
13724                self.write("CAST('");
13725                self.write(d);
13726                self.write("' AS DATE)");
13727            }
13728            // PostgreSQL, MySQL, Redshift: DATE 'value' -> CAST('value' AS DATE)
13729            Some(DialectType::PostgreSQL)
13730            | Some(DialectType::MySQL)
13731            | Some(DialectType::SingleStore)
13732            | Some(DialectType::TiDB)
13733            | Some(DialectType::Redshift) => {
13734                self.write("CAST('");
13735                self.write(d);
13736                self.write("' AS DATE)");
13737            }
13738            // DuckDB, Presto, Trino, Spark: DATE 'value' -> CAST('value' AS DATE)
13739            Some(DialectType::DuckDB)
13740            | Some(DialectType::Presto)
13741            | Some(DialectType::Trino)
13742            | Some(DialectType::Athena)
13743            | Some(DialectType::Spark)
13744            | Some(DialectType::Databricks)
13745            | Some(DialectType::Hive) => {
13746                self.write("CAST('");
13747                self.write(d);
13748                self.write("' AS DATE)");
13749            }
13750            // Oracle: DATE 'value' -> TO_DATE('value', 'YYYY-MM-DD')
13751            Some(DialectType::Oracle) => {
13752                self.write("TO_DATE('");
13753                self.write(d);
13754                self.write("', 'YYYY-MM-DD')");
13755            }
13756            // Standard SQL: DATE '...'
13757            _ => {
13758                self.write_keyword("DATE");
13759                self.write(" '");
13760                self.write(d);
13761                self.write("'");
13762            }
13763        }
13764        Ok(())
13765    }
13766
13767    /// Generate a TIME literal with dialect-specific formatting
13768    fn generate_time_literal(&mut self, t: &str) -> Result<()> {
13769        use crate::dialects::DialectType;
13770
13771        match self.config.dialect {
13772            // SQL Server uses CONVERT or CAST
13773            Some(DialectType::TSQL) => {
13774                self.write("CAST('");
13775                self.write(t);
13776                self.write("' AS TIME)");
13777            }
13778            // Standard SQL: TIME '...'
13779            _ => {
13780                self.write_keyword("TIME");
13781                self.write(" '");
13782                self.write(t);
13783                self.write("'");
13784            }
13785        }
13786        Ok(())
13787    }
13788
13789    /// Generate a date expression for Dremio, converting DATE literals to CAST
13790    fn generate_dremio_date_expression(&mut self, expr: &Expression) -> Result<()> {
13791        use crate::expressions::Literal;
13792
13793        match expr {
13794            Expression::Literal(Literal::Date(d)) => {
13795                // DATE 'value' -> CAST('value' AS DATE)
13796                self.write("CAST('");
13797                self.write(d);
13798                self.write("' AS DATE)");
13799            }
13800            _ => {
13801                // For all other expressions, generate normally
13802                self.generate_expression(expr)?;
13803            }
13804        }
13805        Ok(())
13806    }
13807
13808    /// Generate a TIMESTAMP literal with dialect-specific formatting
13809    fn generate_timestamp_literal(&mut self, ts: &str) -> Result<()> {
13810        use crate::dialects::DialectType;
13811
13812        match self.config.dialect {
13813            // SQL Server uses CONVERT or CAST
13814            Some(DialectType::TSQL) => {
13815                self.write("CAST('");
13816                self.write(ts);
13817                self.write("' AS DATETIME2)");
13818            }
13819            // BigQuery uses CAST syntax for type literals
13820            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
13821            Some(DialectType::BigQuery) => {
13822                self.write("CAST('");
13823                self.write(ts);
13824                self.write("' AS TIMESTAMP)");
13825            }
13826            // Snowflake uses CAST syntax for TIMESTAMP literals
13827            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
13828            Some(DialectType::Snowflake) => {
13829                self.write("CAST('");
13830                self.write(ts);
13831                self.write("' AS TIMESTAMP)");
13832            }
13833            // Dremio uses CAST syntax for TIMESTAMP literals
13834            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
13835            Some(DialectType::Dremio) => {
13836                self.write("CAST('");
13837                self.write(ts);
13838                self.write("' AS TIMESTAMP)");
13839            }
13840            // Exasol uses CAST syntax for TIMESTAMP literals
13841            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
13842            Some(DialectType::Exasol) => {
13843                self.write("CAST('");
13844                self.write(ts);
13845                self.write("' AS TIMESTAMP)");
13846            }
13847            // Oracle prefers TO_TIMESTAMP function call
13848            // TIMESTAMP 'value' -> TO_TIMESTAMP('value', 'YYYY-MM-DD HH24:MI:SS.FF6')
13849            Some(DialectType::Oracle) => {
13850                self.write("TO_TIMESTAMP('");
13851                self.write(ts);
13852                self.write("', 'YYYY-MM-DD HH24:MI:SS.FF6')");
13853            }
13854            // Presto/Trino: always use CAST for TIMESTAMP literals
13855            Some(DialectType::Presto) | Some(DialectType::Trino) => {
13856                if Self::timestamp_has_timezone(ts) {
13857                    self.write("CAST('");
13858                    self.write(ts);
13859                    self.write("' AS TIMESTAMP WITH TIME ZONE)");
13860                } else {
13861                    self.write("CAST('");
13862                    self.write(ts);
13863                    self.write("' AS TIMESTAMP)");
13864                }
13865            }
13866            // ClickHouse: CAST('...' AS Nullable(DateTime))
13867            Some(DialectType::ClickHouse) => {
13868                self.write("CAST('");
13869                self.write(ts);
13870                self.write("' AS Nullable(DateTime))");
13871            }
13872            // Spark: CAST('...' AS TIMESTAMP)
13873            Some(DialectType::Spark) => {
13874                self.write("CAST('");
13875                self.write(ts);
13876                self.write("' AS TIMESTAMP)");
13877            }
13878            // Redshift: CAST('...' AS TIMESTAMP) for regular timestamps,
13879            // but TIMESTAMP '...' for special values like 'epoch'
13880            Some(DialectType::Redshift) => {
13881                if ts == "epoch" {
13882                    self.write_keyword("TIMESTAMP");
13883                    self.write(" '");
13884                    self.write(ts);
13885                    self.write("'");
13886                } else {
13887                    self.write("CAST('");
13888                    self.write(ts);
13889                    self.write("' AS TIMESTAMP)");
13890                }
13891            }
13892            // PostgreSQL, Hive, DuckDB, etc.: CAST('...' AS TIMESTAMP)
13893            Some(DialectType::PostgreSQL)
13894            | Some(DialectType::Hive)
13895            | Some(DialectType::SQLite)
13896            | Some(DialectType::DuckDB)
13897            | Some(DialectType::Athena)
13898            | Some(DialectType::Drill)
13899            | Some(DialectType::Teradata) => {
13900                self.write("CAST('");
13901                self.write(ts);
13902                self.write("' AS TIMESTAMP)");
13903            }
13904            // MySQL/StarRocks: CAST('...' AS DATETIME)
13905            Some(DialectType::MySQL) | Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
13906                self.write("CAST('");
13907                self.write(ts);
13908                self.write("' AS DATETIME)");
13909            }
13910            // Databricks: CAST('...' AS TIMESTAMP_NTZ)
13911            Some(DialectType::Databricks) => {
13912                self.write("CAST('");
13913                self.write(ts);
13914                self.write("' AS TIMESTAMP_NTZ)");
13915            }
13916            // Standard SQL: TIMESTAMP '...'
13917            _ => {
13918                self.write_keyword("TIMESTAMP");
13919                self.write(" '");
13920                self.write(ts);
13921                self.write("'");
13922            }
13923        }
13924        Ok(())
13925    }
13926
13927    /// Check if a timestamp string contains a timezone identifier
13928    /// This detects IANA timezone names like Europe/Prague, America/New_York, etc.
13929    fn timestamp_has_timezone(ts: &str) -> bool {
13930        // Check for common IANA timezone patterns: Continent/City format
13931        // Examples: Europe/Prague, America/New_York, Asia/Tokyo, etc.
13932        // Also handles: UTC, GMT, Etc/GMT+0, etc.
13933        let ts_lower = ts.to_lowercase();
13934
13935        // Check for Continent/City pattern (most common)
13936        let continent_prefixes = [
13937            "africa/",
13938            "america/",
13939            "antarctica/",
13940            "arctic/",
13941            "asia/",
13942            "atlantic/",
13943            "australia/",
13944            "europe/",
13945            "indian/",
13946            "pacific/",
13947            "etc/",
13948            "brazil/",
13949            "canada/",
13950            "chile/",
13951            "mexico/",
13952            "us/",
13953        ];
13954
13955        for prefix in &continent_prefixes {
13956            if ts_lower.contains(prefix) {
13957                return true;
13958            }
13959        }
13960
13961        // Check for standalone timezone abbreviations at the end
13962        // These typically appear after the time portion
13963        let tz_abbrevs = [
13964            " utc", " gmt", " cet", " cest", " eet", " eest", " wet", " west", " est", " edt",
13965            " cst", " cdt", " mst", " mdt", " pst", " pdt", " ist", " bst", " jst", " kst", " hkt",
13966            " sgt", " aest", " aedt", " acst", " acdt", " awst",
13967        ];
13968
13969        for abbrev in &tz_abbrevs {
13970            if ts_lower.ends_with(abbrev) {
13971                return true;
13972            }
13973        }
13974
13975        // Check for numeric timezone offsets: +N, -N, +NN:NN, -NN:NN
13976        // Examples: "2012-10-31 01:00 -2", "2012-10-31 01:00 +02:00"
13977        // Look for pattern: space followed by + or - and digits (optionally with :)
13978        let trimmed = ts.trim();
13979        if let Some(last_space) = trimmed.rfind(' ') {
13980            let suffix = &trimmed[last_space + 1..];
13981            if (suffix.starts_with('+') || suffix.starts_with('-')) && suffix.len() > 1 {
13982                // Check if rest is numeric (possibly with : for hh:mm format)
13983                let rest = &suffix[1..];
13984                if rest.chars().all(|c| c.is_ascii_digit() || c == ':') {
13985                    return true;
13986                }
13987            }
13988        }
13989
13990        false
13991    }
13992
13993    /// Generate a DATETIME literal with dialect-specific formatting
13994    fn generate_datetime_literal(&mut self, dt: &str) -> Result<()> {
13995        use crate::dialects::DialectType;
13996
13997        match self.config.dialect {
13998            // BigQuery uses CAST syntax for type literals
13999            // DATETIME 'value' -> CAST('value' AS DATETIME)
14000            Some(DialectType::BigQuery) => {
14001                self.write("CAST('");
14002                self.write(dt);
14003                self.write("' AS DATETIME)");
14004            }
14005            // DuckDB: DATETIME -> CAST('value' AS TIMESTAMP)
14006            Some(DialectType::DuckDB) => {
14007                self.write("CAST('");
14008                self.write(dt);
14009                self.write("' AS TIMESTAMP)");
14010            }
14011            // DATETIME is primarily a BigQuery type
14012            // Output as DATETIME '...' for dialects that support it
14013            _ => {
14014                self.write_keyword("DATETIME");
14015                self.write(" '");
14016                self.write(dt);
14017                self.write("'");
14018            }
14019        }
14020        Ok(())
14021    }
14022
14023    /// Generate a string literal with dialect-specific escaping
14024    fn generate_string_literal(&mut self, s: &str) -> Result<()> {
14025        use crate::dialects::DialectType;
14026
14027        match self.config.dialect {
14028            // MySQL/Hive: Uses SQL standard quote escaping ('') for quotes,
14029            // and backslash escaping for special characters like newlines
14030            // Hive STRING_ESCAPES = ["\\"] - uses backslash escapes
14031            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
14032                // Hive/Spark use backslash escaping for quotes (\') and special chars
14033                self.write("'");
14034                for c in s.chars() {
14035                    match c {
14036                        '\'' => self.write("\\'"),
14037                        '\\' => self.write("\\\\"),
14038                        '\n' => self.write("\\n"),
14039                        '\r' => self.write("\\r"),
14040                        '\t' => self.write("\\t"),
14041                        '\0' => self.write("\\0"),
14042                        _ => self.output.push(c),
14043                    }
14044                }
14045                self.write("'");
14046            }
14047            Some(DialectType::Drill) => {
14048                // Drill uses SQL-standard quote doubling ('') for quotes,
14049                // but backslash escaping for special characters
14050                self.write("'");
14051                for c in s.chars() {
14052                    match c {
14053                        '\'' => self.write("''"),
14054                        '\\' => self.write("\\\\"),
14055                        '\n' => self.write("\\n"),
14056                        '\r' => self.write("\\r"),
14057                        '\t' => self.write("\\t"),
14058                        '\0' => self.write("\\0"),
14059                        _ => self.output.push(c),
14060                    }
14061                }
14062                self.write("'");
14063            }
14064            Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB) => {
14065                self.write("'");
14066                for c in s.chars() {
14067                    match c {
14068                        // MySQL uses SQL standard quote doubling
14069                        '\'' => self.write("''"),
14070                        '\\' => self.write("\\\\"),
14071                        '\n' => self.write("\\n"),
14072                        '\r' => self.write("\\r"),
14073                        '\t' => self.write("\\t"),
14074                        // sqlglot writes a literal NUL for this case
14075                        '\0' => self.output.push('\0'),
14076                        _ => self.output.push(c),
14077                    }
14078                }
14079                self.write("'");
14080            }
14081            // BigQuery: Uses backslash escaping
14082            Some(DialectType::BigQuery) => {
14083                self.write("'");
14084                for c in s.chars() {
14085                    match c {
14086                        '\'' => self.write("\\'"),
14087                        '\\' => self.write("\\\\"),
14088                        '\n' => self.write("\\n"),
14089                        '\r' => self.write("\\r"),
14090                        '\t' => self.write("\\t"),
14091                        '\0' => self.write("\\0"),
14092                        '\x07' => self.write("\\a"),
14093                        '\x08' => self.write("\\b"),
14094                        '\x0C' => self.write("\\f"),
14095                        '\x0B' => self.write("\\v"),
14096                        _ => self.output.push(c),
14097                    }
14098                }
14099                self.write("'");
14100            }
14101            // Athena: Uses different escaping for DDL (Hive) vs DML (Trino)
14102            // In Hive context (DDL): backslash escaping for single quotes (\') and backslashes (\\)
14103            // In Trino context (DML): SQL-standard escaping ('') and literal backslashes
14104            Some(DialectType::Athena) => {
14105                if self.athena_hive_context {
14106                    // Hive-style: backslash escaping
14107                    self.write("'");
14108                    for c in s.chars() {
14109                        match c {
14110                            '\'' => self.write("\\'"),
14111                            '\\' => self.write("\\\\"),
14112                            '\n' => self.write("\\n"),
14113                            '\r' => self.write("\\r"),
14114                            '\t' => self.write("\\t"),
14115                            '\0' => self.write("\\0"),
14116                            _ => self.output.push(c),
14117                        }
14118                    }
14119                    self.write("'");
14120                } else {
14121                    // Trino-style: SQL-standard escaping, preserve backslashes
14122                    self.write("'");
14123                    for c in s.chars() {
14124                        match c {
14125                            '\'' => self.write("''"),
14126                            // Preserve backslashes literally (no re-escaping)
14127                            _ => self.output.push(c),
14128                        }
14129                    }
14130                    self.write("'");
14131                }
14132            }
14133            // Snowflake: Uses backslash escaping (STRING_ESCAPES = ["\\", "'"])
14134            // The tokenizer preserves backslash escape sequences literally (e.g., input '\\'
14135            // becomes string value '\\'), so we should NOT re-escape backslashes.
14136            // We only need to escape single quotes.
14137            Some(DialectType::Snowflake) => {
14138                self.write("'");
14139                for c in s.chars() {
14140                    match c {
14141                        '\'' => self.write("\\'"),
14142                        // Backslashes are already escaped in the tokenized string, don't re-escape
14143                        // Only escape special characters that might not have been escaped
14144                        '\n' => self.write("\\n"),
14145                        '\r' => self.write("\\r"),
14146                        '\t' => self.write("\\t"),
14147                        _ => self.output.push(c),
14148                    }
14149                }
14150                self.write("'");
14151            }
14152            // PostgreSQL: Output special characters as literal chars in strings (no E-string prefix)
14153            Some(DialectType::PostgreSQL) => {
14154                self.write("'");
14155                for c in s.chars() {
14156                    match c {
14157                        '\'' => self.write("''"),
14158                        _ => self.output.push(c),
14159                    }
14160                }
14161                self.write("'");
14162            }
14163            // Redshift: Uses backslash escaping for single quotes
14164            Some(DialectType::Redshift) => {
14165                self.write("'");
14166                for c in s.chars() {
14167                    match c {
14168                        '\'' => self.write("\\'"),
14169                        _ => self.output.push(c),
14170                    }
14171                }
14172                self.write("'");
14173            }
14174            // Oracle: Uses standard double single-quote escaping
14175            Some(DialectType::Oracle) => {
14176                self.write("'");
14177                self.write(&s.replace('\'', "''"));
14178                self.write("'");
14179            }
14180            // ClickHouse: Uses SQL-standard quote doubling ('') for quotes,
14181            // backslash escaping for backslashes and special characters
14182            Some(DialectType::ClickHouse) => {
14183                self.write("'");
14184                for c in s.chars() {
14185                    match c {
14186                        '\'' => self.write("''"),
14187                        '\\' => self.write("\\\\"),
14188                        '\n' => self.write("\\n"),
14189                        '\r' => self.write("\\r"),
14190                        '\t' => self.write("\\t"),
14191                        '\0' => self.write("\\0"),
14192                        '\x07' => self.write("\\a"),
14193                        '\x08' => self.write("\\b"),
14194                        '\x0C' => self.write("\\f"),
14195                        '\x0B' => self.write("\\v"),
14196                        // Non-printable characters: emit as \xNN hex escapes
14197                        c if c.is_control() || (c as u32) < 0x20 => {
14198                            let byte = c as u32;
14199                            if byte < 256 {
14200                                self.write(&format!("\\x{:02X}", byte));
14201                            } else {
14202                                self.output.push(c);
14203                            }
14204                        }
14205                        _ => self.output.push(c),
14206                    }
14207                }
14208                self.write("'");
14209            }
14210            // Default: SQL standard double single quotes (works for most dialects)
14211            // PostgreSQL, Snowflake, DuckDB, TSQL, etc.
14212            _ => {
14213                self.write("'");
14214                self.write(&s.replace('\'', "''"));
14215                self.write("'");
14216            }
14217        }
14218        Ok(())
14219    }
14220
14221    /// Write a byte string with proper escaping for BigQuery-style byte literals
14222    /// Escapes characters as \xNN hex escapes where needed
14223    fn write_escaped_byte_string(&mut self, s: &str) {
14224        for c in s.chars() {
14225            match c {
14226                // Escape single quotes
14227                '\'' => self.write("\\'"),
14228                // Escape backslashes
14229                '\\' => self.write("\\\\"),
14230                // Keep all printable characters (including non-ASCII) as-is
14231                _ if !c.is_control() => self.output.push(c),
14232                // Escape control characters as hex
14233                _ => {
14234                    let byte = c as u32;
14235                    if byte < 256 {
14236                        self.write(&format!("\\x{:02x}", byte));
14237                    } else {
14238                        // For unicode characters, write each UTF-8 byte
14239                        for b in c.to_string().as_bytes() {
14240                            self.write(&format!("\\x{:02x}", b));
14241                        }
14242                    }
14243                }
14244            }
14245        }
14246    }
14247
14248    fn generate_boolean(&mut self, b: &BooleanLiteral) -> Result<()> {
14249        use crate::dialects::DialectType;
14250
14251        // Different dialects have different boolean literal formats
14252        match self.config.dialect {
14253            // SQL Server typically uses 1/0 for boolean literals in many contexts
14254            // However, TRUE/FALSE also works in modern versions
14255            Some(DialectType::TSQL) => {
14256                self.write(if b.value { "1" } else { "0" });
14257            }
14258            // Oracle traditionally uses 1/0 (no native boolean until recent versions)
14259            Some(DialectType::Oracle) => {
14260                self.write(if b.value { "1" } else { "0" });
14261            }
14262            // MySQL accepts TRUE/FALSE as aliases for 1/0
14263            Some(DialectType::MySQL) => {
14264                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
14265            }
14266            // Most other dialects support TRUE/FALSE
14267            _ => {
14268                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
14269            }
14270        }
14271        Ok(())
14272    }
14273
14274    /// Generate an identifier that's used as an alias name
14275    /// This quotes reserved keywords in addition to already-quoted identifiers
14276    fn generate_alias_identifier(&mut self, id: &Identifier) -> Result<()> {
14277        let name = &id.name;
14278        let quote_style = &self.config.identifier_quote_style;
14279
14280        // For aliases, quote if:
14281        // 1. The identifier was explicitly quoted in the source
14282        // 2. The identifier is a reserved keyword for the current dialect
14283        let needs_quoting = id.quoted || self.is_reserved_keyword(name);
14284
14285        // Normalize identifier if configured
14286        let output_name = if self.config.normalize_identifiers && !id.quoted {
14287            name.to_lowercase()
14288        } else {
14289            name.to_string()
14290        };
14291
14292        if needs_quoting {
14293            // Escape any quote characters within the identifier
14294            let escaped_name = if quote_style.start == quote_style.end {
14295                output_name.replace(
14296                    quote_style.end,
14297                    &format!("{}{}", quote_style.end, quote_style.end),
14298                )
14299            } else {
14300                output_name.replace(
14301                    quote_style.end,
14302                    &format!("{}{}", quote_style.end, quote_style.end),
14303                )
14304            };
14305            self.write(&format!(
14306                "{}{}{}",
14307                quote_style.start, escaped_name, quote_style.end
14308            ));
14309        } else {
14310            self.write(&output_name);
14311        }
14312
14313        // Output trailing comments
14314        for comment in &id.trailing_comments {
14315            self.write(" ");
14316            self.write_formatted_comment(comment);
14317        }
14318        Ok(())
14319    }
14320
14321    fn generate_identifier(&mut self, id: &Identifier) -> Result<()> {
14322        use crate::dialects::DialectType;
14323
14324        let name = &id.name;
14325
14326        // For Athena, use backticks in Hive context, double quotes in Trino context
14327        let quote_style = if matches!(self.config.dialect, Some(DialectType::Athena))
14328            && self.athena_hive_context
14329        {
14330            &IdentifierQuoteStyle::BACKTICK
14331        } else {
14332            &self.config.identifier_quote_style
14333        };
14334
14335        // Quote if:
14336        // 1. The identifier was explicitly quoted in the source
14337        // 2. The identifier is a reserved keyword for the current dialect
14338        // 3. The config says to always quote identifiers (e.g., Athena/Presto)
14339        // This matches Python sqlglot's identifier_sql behavior
14340        // Also quote identifiers starting with digits if the target dialect doesn't support them
14341        let starts_with_digit = name.chars().next().map_or(false, |c| c.is_ascii_digit());
14342        let needs_digit_quoting = starts_with_digit
14343            && !self.config.identifiers_can_start_with_digit
14344            && self.config.dialect.is_some();
14345        let mysql_invalid_hex_identifier = matches!(self.config.dialect, Some(DialectType::MySQL))
14346            && name.len() > 2
14347            && (name.starts_with("0x") || name.starts_with("0X"))
14348            && !name[2..].chars().all(|c| c.is_ascii_hexdigit());
14349        let needs_quoting = id.quoted
14350            || self.is_reserved_keyword(name)
14351            || self.config.always_quote_identifiers
14352            || needs_digit_quoting
14353            || mysql_invalid_hex_identifier;
14354
14355        // Check for MySQL index column prefix length: name(16) or name(16) ASC/DESC
14356        // When quoted, we need to output `name`(16) not `name(16)`
14357        let (base_name, suffix) = if needs_quoting {
14358            // Try to extract prefix length from identifier: name(number) or name(number) ASC/DESC
14359            if let Some(paren_pos) = name.find('(') {
14360                let base = &name[..paren_pos];
14361                let rest = &name[paren_pos..];
14362                // Verify it looks like (digits) or (digits) ASC/DESC
14363                if rest.starts_with('(')
14364                    && (rest.ends_with(')') || rest.ends_with(") ASC") || rest.ends_with(") DESC"))
14365                {
14366                    // Check if content between parens is all digits
14367                    let close_paren = rest.find(')').unwrap_or(rest.len());
14368                    let inside = &rest[1..close_paren];
14369                    if inside.chars().all(|c| c.is_ascii_digit()) {
14370                        (base.to_string(), rest.to_string())
14371                    } else {
14372                        (name.to_string(), String::new())
14373                    }
14374                } else {
14375                    (name.to_string(), String::new())
14376                }
14377            } else if name.ends_with(" ASC") {
14378                let base = &name[..name.len() - 4];
14379                (base.to_string(), " ASC".to_string())
14380            } else if name.ends_with(" DESC") {
14381                let base = &name[..name.len() - 5];
14382                (base.to_string(), " DESC".to_string())
14383            } else {
14384                (name.to_string(), String::new())
14385            }
14386        } else {
14387            (name.to_string(), String::new())
14388        };
14389
14390        // Normalize identifier if configured, with special handling for Exasol
14391        // Exasol uses UPPERCASE normalization strategy, so reserved keywords that need quoting
14392        // should be uppercased when not already quoted (to match Python sqlglot behavior)
14393        let output_name = if self.config.normalize_identifiers && !id.quoted {
14394            base_name.to_lowercase()
14395        } else if matches!(self.config.dialect, Some(DialectType::Exasol))
14396            && !id.quoted
14397            && self.is_reserved_keyword(name)
14398        {
14399            // Exasol: uppercase reserved keywords when quoting them
14400            // This matches Python sqlglot's behavior with NORMALIZATION_STRATEGY = UPPERCASE
14401            base_name.to_uppercase()
14402        } else {
14403            base_name
14404        };
14405
14406        if needs_quoting {
14407            // Escape any quote characters within the identifier
14408            let escaped_name = if quote_style.start == quote_style.end {
14409                // Same start/end char (e.g., " or `) - double the quote char
14410                output_name.replace(
14411                    quote_style.end,
14412                    &format!("{}{}", quote_style.end, quote_style.end),
14413                )
14414            } else {
14415                // Different start/end (e.g., [ and ]) - escape only the end char
14416                output_name.replace(
14417                    quote_style.end,
14418                    &format!("{}{}", quote_style.end, quote_style.end),
14419                )
14420            };
14421            self.write(&format!(
14422                "{}{}{}{}",
14423                quote_style.start, escaped_name, quote_style.end, suffix
14424            ));
14425        } else {
14426            self.write(&output_name);
14427        }
14428
14429        // Output trailing comments
14430        for comment in &id.trailing_comments {
14431            self.write(" ");
14432            self.write_formatted_comment(comment);
14433        }
14434        Ok(())
14435    }
14436
14437    fn generate_column(&mut self, col: &Column) -> Result<()> {
14438        use crate::dialects::DialectType;
14439
14440        if let Some(table) = &col.table {
14441            // Exasol special case: LOCAL as column table prefix should NOT be quoted
14442            // LOCAL is a special keyword in Exasol for referencing aliases from the current scope
14443            // Only applies when: dialect is Exasol, name is "LOCAL" (case-insensitive), and not already quoted
14444            let is_exasol_local_prefix = matches!(self.config.dialect, Some(DialectType::Exasol))
14445                && !table.quoted
14446                && table.name.eq_ignore_ascii_case("LOCAL");
14447
14448            if is_exasol_local_prefix {
14449                // Write LOCAL unquoted (this is special Exasol syntax, not a table reference)
14450                self.write("LOCAL");
14451            } else {
14452                self.generate_identifier(table)?;
14453            }
14454            self.write(".");
14455        }
14456        self.generate_identifier(&col.name)?;
14457        // Oracle-style join marker (+)
14458        // Only output if dialect supports it (Oracle, Exasol)
14459        if col.join_mark && self.config.supports_column_join_marks {
14460            self.write(" (+)");
14461        }
14462        // Output trailing comments
14463        for comment in &col.trailing_comments {
14464            self.write_space();
14465            self.write_formatted_comment(comment);
14466        }
14467        Ok(())
14468    }
14469
14470    /// Generate a pseudocolumn (Oracle ROWNUM, ROWID, LEVEL, etc.)
14471    /// Pseudocolumns should NEVER be quoted, as quoting breaks them in Oracle
14472    fn generate_pseudocolumn(&mut self, pc: &Pseudocolumn) -> Result<()> {
14473        use crate::dialects::DialectType;
14474        use crate::expressions::PseudocolumnType;
14475
14476        // SYSDATE -> CURRENT_TIMESTAMP for non-Oracle/Redshift dialects
14477        if pc.kind == PseudocolumnType::Sysdate
14478            && !matches!(
14479                self.config.dialect,
14480                Some(DialectType::Oracle) | Some(DialectType::Redshift) | None
14481            )
14482        {
14483            self.write_keyword("CURRENT_TIMESTAMP");
14484            // Add () for dialects that expect it
14485            if matches!(
14486                self.config.dialect,
14487                Some(DialectType::MySQL)
14488                    | Some(DialectType::ClickHouse)
14489                    | Some(DialectType::Spark)
14490                    | Some(DialectType::Databricks)
14491                    | Some(DialectType::Hive)
14492            ) {
14493                self.write("()");
14494            }
14495        } else {
14496            self.write(pc.kind.as_str());
14497        }
14498        Ok(())
14499    }
14500
14501    /// Generate CONNECT BY clause (Oracle hierarchical queries)
14502    fn generate_connect(&mut self, connect: &Connect) -> Result<()> {
14503        use crate::dialects::DialectType;
14504
14505        // Generate native CONNECT BY for Oracle and Snowflake
14506        // For other dialects, add a comment noting manual conversion needed
14507        let supports_connect_by = matches!(
14508            self.config.dialect,
14509            Some(DialectType::Oracle) | Some(DialectType::Snowflake)
14510        );
14511
14512        if !supports_connect_by && self.config.dialect.is_some() {
14513            // Add comment for unsupported dialects
14514            if self.config.pretty {
14515                self.write_newline();
14516            } else {
14517                self.write_space();
14518            }
14519            self.write("/* CONNECT BY requires manual conversion to recursive CTE */");
14520        }
14521
14522        // Generate START WITH if present (before CONNECT BY)
14523        if let Some(start) = &connect.start {
14524            if self.config.pretty {
14525                self.write_newline();
14526            } else {
14527                self.write_space();
14528            }
14529            self.write_keyword("START WITH");
14530            self.write_space();
14531            self.generate_expression(start)?;
14532        }
14533
14534        // Generate CONNECT BY
14535        if self.config.pretty {
14536            self.write_newline();
14537        } else {
14538            self.write_space();
14539        }
14540        self.write_keyword("CONNECT BY");
14541        if connect.nocycle {
14542            self.write_space();
14543            self.write_keyword("NOCYCLE");
14544        }
14545        self.write_space();
14546        self.generate_expression(&connect.connect)?;
14547
14548        Ok(())
14549    }
14550
14551    /// Generate Connect expression (for Expression::Connect variant)
14552    fn generate_connect_expr(&mut self, connect: &Connect) -> Result<()> {
14553        self.generate_connect(connect)
14554    }
14555
14556    /// Generate PRIOR expression
14557    fn generate_prior(&mut self, prior: &Prior) -> Result<()> {
14558        self.write_keyword("PRIOR");
14559        self.write_space();
14560        self.generate_expression(&prior.this)?;
14561        Ok(())
14562    }
14563
14564    /// Generate CONNECT_BY_ROOT function
14565    /// Syntax: CONNECT_BY_ROOT column (no parentheses)
14566    fn generate_connect_by_root(&mut self, cbr: &ConnectByRoot) -> Result<()> {
14567        self.write_keyword("CONNECT_BY_ROOT");
14568        self.write_space();
14569        self.generate_expression(&cbr.this)?;
14570        Ok(())
14571    }
14572
14573    /// Generate MATCH_RECOGNIZE clause
14574    fn generate_match_recognize(&mut self, mr: &MatchRecognize) -> Result<()> {
14575        use crate::dialects::DialectType;
14576
14577        // MATCH_RECOGNIZE is supported in Oracle, Snowflake, Presto, and Trino
14578        let supports_match_recognize = matches!(
14579            self.config.dialect,
14580            Some(DialectType::Oracle)
14581                | Some(DialectType::Snowflake)
14582                | Some(DialectType::Presto)
14583                | Some(DialectType::Trino)
14584        );
14585
14586        // Generate the source table first
14587        if let Some(source) = &mr.this {
14588            self.generate_expression(source)?;
14589        }
14590
14591        if !supports_match_recognize {
14592            self.write("/* MATCH_RECOGNIZE not supported in this dialect */");
14593            return Ok(());
14594        }
14595
14596        // In pretty mode, MATCH_RECOGNIZE should be on a new line
14597        if self.config.pretty {
14598            self.write_newline();
14599        } else {
14600            self.write_space();
14601        }
14602
14603        self.write_keyword("MATCH_RECOGNIZE");
14604        self.write(" (");
14605
14606        if self.config.pretty {
14607            self.indent_level += 1;
14608        }
14609
14610        let mut needs_separator = false;
14611
14612        // PARTITION BY
14613        if let Some(partition_by) = &mr.partition_by {
14614            if !partition_by.is_empty() {
14615                if self.config.pretty {
14616                    self.write_newline();
14617                    self.write_indent();
14618                }
14619                self.write_keyword("PARTITION BY");
14620                self.write_space();
14621                for (i, expr) in partition_by.iter().enumerate() {
14622                    if i > 0 {
14623                        self.write(", ");
14624                    }
14625                    self.generate_expression(expr)?;
14626                }
14627                needs_separator = true;
14628            }
14629        }
14630
14631        // ORDER BY
14632        if let Some(order_by) = &mr.order_by {
14633            if !order_by.is_empty() {
14634                if needs_separator {
14635                    if self.config.pretty {
14636                        self.write_newline();
14637                        self.write_indent();
14638                    } else {
14639                        self.write_space();
14640                    }
14641                } else if self.config.pretty {
14642                    self.write_newline();
14643                    self.write_indent();
14644                }
14645                self.write_keyword("ORDER BY");
14646                // In pretty mode, put each ORDER BY column on a new indented line
14647                if self.config.pretty {
14648                    self.indent_level += 1;
14649                    for (i, ordered) in order_by.iter().enumerate() {
14650                        if i > 0 {
14651                            self.write(",");
14652                        }
14653                        self.write_newline();
14654                        self.write_indent();
14655                        self.generate_ordered(ordered)?;
14656                    }
14657                    self.indent_level -= 1;
14658                } else {
14659                    self.write_space();
14660                    for (i, ordered) in order_by.iter().enumerate() {
14661                        if i > 0 {
14662                            self.write(", ");
14663                        }
14664                        self.generate_ordered(ordered)?;
14665                    }
14666                }
14667                needs_separator = true;
14668            }
14669        }
14670
14671        // MEASURES
14672        if let Some(measures) = &mr.measures {
14673            if !measures.is_empty() {
14674                if needs_separator {
14675                    if self.config.pretty {
14676                        self.write_newline();
14677                        self.write_indent();
14678                    } else {
14679                        self.write_space();
14680                    }
14681                } else if self.config.pretty {
14682                    self.write_newline();
14683                    self.write_indent();
14684                }
14685                self.write_keyword("MEASURES");
14686                // In pretty mode, put each MEASURE on a new indented line
14687                if self.config.pretty {
14688                    self.indent_level += 1;
14689                    for (i, measure) in measures.iter().enumerate() {
14690                        if i > 0 {
14691                            self.write(",");
14692                        }
14693                        self.write_newline();
14694                        self.write_indent();
14695                        // Handle RUNNING/FINAL prefix
14696                        if let Some(semantics) = &measure.window_frame {
14697                            match semantics {
14698                                MatchRecognizeSemantics::Running => {
14699                                    self.write_keyword("RUNNING");
14700                                    self.write_space();
14701                                }
14702                                MatchRecognizeSemantics::Final => {
14703                                    self.write_keyword("FINAL");
14704                                    self.write_space();
14705                                }
14706                            }
14707                        }
14708                        self.generate_expression(&measure.this)?;
14709                    }
14710                    self.indent_level -= 1;
14711                } else {
14712                    self.write_space();
14713                    for (i, measure) in measures.iter().enumerate() {
14714                        if i > 0 {
14715                            self.write(", ");
14716                        }
14717                        // Handle RUNNING/FINAL prefix
14718                        if let Some(semantics) = &measure.window_frame {
14719                            match semantics {
14720                                MatchRecognizeSemantics::Running => {
14721                                    self.write_keyword("RUNNING");
14722                                    self.write_space();
14723                                }
14724                                MatchRecognizeSemantics::Final => {
14725                                    self.write_keyword("FINAL");
14726                                    self.write_space();
14727                                }
14728                            }
14729                        }
14730                        self.generate_expression(&measure.this)?;
14731                    }
14732                }
14733                needs_separator = true;
14734            }
14735        }
14736
14737        // Row semantics (ONE ROW PER MATCH, ALL ROWS PER MATCH, etc.)
14738        if let Some(rows) = &mr.rows {
14739            if needs_separator {
14740                if self.config.pretty {
14741                    self.write_newline();
14742                    self.write_indent();
14743                } else {
14744                    self.write_space();
14745                }
14746            } else if self.config.pretty {
14747                self.write_newline();
14748                self.write_indent();
14749            }
14750            match rows {
14751                MatchRecognizeRows::OneRowPerMatch => {
14752                    self.write_keyword("ONE ROW PER MATCH");
14753                }
14754                MatchRecognizeRows::AllRowsPerMatch => {
14755                    self.write_keyword("ALL ROWS PER MATCH");
14756                }
14757                MatchRecognizeRows::AllRowsPerMatchShowEmptyMatches => {
14758                    self.write_keyword("ALL ROWS PER MATCH SHOW EMPTY MATCHES");
14759                }
14760                MatchRecognizeRows::AllRowsPerMatchOmitEmptyMatches => {
14761                    self.write_keyword("ALL ROWS PER MATCH OMIT EMPTY MATCHES");
14762                }
14763                MatchRecognizeRows::AllRowsPerMatchWithUnmatchedRows => {
14764                    self.write_keyword("ALL ROWS PER MATCH WITH UNMATCHED ROWS");
14765                }
14766            }
14767            needs_separator = true;
14768        }
14769
14770        // AFTER MATCH SKIP
14771        if let Some(after) = &mr.after {
14772            if needs_separator {
14773                if self.config.pretty {
14774                    self.write_newline();
14775                    self.write_indent();
14776                } else {
14777                    self.write_space();
14778                }
14779            } else if self.config.pretty {
14780                self.write_newline();
14781                self.write_indent();
14782            }
14783            match after {
14784                MatchRecognizeAfter::PastLastRow => {
14785                    self.write_keyword("AFTER MATCH SKIP PAST LAST ROW");
14786                }
14787                MatchRecognizeAfter::ToNextRow => {
14788                    self.write_keyword("AFTER MATCH SKIP TO NEXT ROW");
14789                }
14790                MatchRecognizeAfter::ToFirst(ident) => {
14791                    self.write_keyword("AFTER MATCH SKIP TO FIRST");
14792                    self.write_space();
14793                    self.generate_identifier(ident)?;
14794                }
14795                MatchRecognizeAfter::ToLast(ident) => {
14796                    self.write_keyword("AFTER MATCH SKIP TO LAST");
14797                    self.write_space();
14798                    self.generate_identifier(ident)?;
14799                }
14800            }
14801            needs_separator = true;
14802        }
14803
14804        // PATTERN
14805        if let Some(pattern) = &mr.pattern {
14806            if needs_separator {
14807                if self.config.pretty {
14808                    self.write_newline();
14809                    self.write_indent();
14810                } else {
14811                    self.write_space();
14812                }
14813            } else if self.config.pretty {
14814                self.write_newline();
14815                self.write_indent();
14816            }
14817            self.write_keyword("PATTERN");
14818            self.write_space();
14819            self.write("(");
14820            self.write(pattern);
14821            self.write(")");
14822            needs_separator = true;
14823        }
14824
14825        // DEFINE
14826        if let Some(define) = &mr.define {
14827            if !define.is_empty() {
14828                if needs_separator {
14829                    if self.config.pretty {
14830                        self.write_newline();
14831                        self.write_indent();
14832                    } else {
14833                        self.write_space();
14834                    }
14835                } else if self.config.pretty {
14836                    self.write_newline();
14837                    self.write_indent();
14838                }
14839                self.write_keyword("DEFINE");
14840                // In pretty mode, put each DEFINE on a new indented line
14841                if self.config.pretty {
14842                    self.indent_level += 1;
14843                    for (i, (name, expr)) in define.iter().enumerate() {
14844                        if i > 0 {
14845                            self.write(",");
14846                        }
14847                        self.write_newline();
14848                        self.write_indent();
14849                        self.generate_identifier(name)?;
14850                        self.write(" AS ");
14851                        self.generate_expression(expr)?;
14852                    }
14853                    self.indent_level -= 1;
14854                } else {
14855                    self.write_space();
14856                    for (i, (name, expr)) in define.iter().enumerate() {
14857                        if i > 0 {
14858                            self.write(", ");
14859                        }
14860                        self.generate_identifier(name)?;
14861                        self.write(" AS ");
14862                        self.generate_expression(expr)?;
14863                    }
14864                }
14865            }
14866        }
14867
14868        if self.config.pretty {
14869            self.indent_level -= 1;
14870            self.write_newline();
14871        }
14872        self.write(")");
14873
14874        // Alias - only include AS if it was explicitly present in the input
14875        if let Some(alias) = &mr.alias {
14876            self.write(" ");
14877            if mr.alias_explicit_as {
14878                self.write_keyword("AS");
14879                self.write(" ");
14880            }
14881            self.generate_identifier(alias)?;
14882        }
14883
14884        Ok(())
14885    }
14886
14887    /// Generate a query hint /*+ ... */
14888    fn generate_hint(&mut self, hint: &Hint) -> Result<()> {
14889        use crate::dialects::DialectType;
14890
14891        // Output hints for dialects that support them, or when no dialect is specified (identity tests)
14892        let supports_hints = matches!(
14893            self.config.dialect,
14894            None |  // No dialect = preserve everything
14895            Some(DialectType::Oracle) | Some(DialectType::MySQL) |
14896            Some(DialectType::Spark) | Some(DialectType::Hive) |
14897            Some(DialectType::Databricks) | Some(DialectType::PostgreSQL)
14898        );
14899
14900        if !supports_hints || hint.expressions.is_empty() {
14901            return Ok(());
14902        }
14903
14904        // First, expand raw hint text into individual hint strings
14905        // This handles the case where the parser stored multiple hints as a single raw string
14906        let mut hint_strings: Vec<String> = Vec::new();
14907        for expr in &hint.expressions {
14908            match expr {
14909                HintExpression::Raw(text) => {
14910                    // Parse raw hint text into individual hint function calls
14911                    let parsed = self.parse_raw_hint_text(text);
14912                    hint_strings.extend(parsed);
14913                }
14914                _ => {
14915                    hint_strings.push(self.hint_expression_to_string(expr)?);
14916                }
14917            }
14918        }
14919
14920        // In pretty mode with multiple hints, always use multiline format
14921        // This matches Python sqlglot's behavior where expressions() with default dynamic=False
14922        // always joins with newlines in pretty mode
14923        let use_multiline = self.config.pretty && hint_strings.len() > 1;
14924
14925        if use_multiline {
14926            // Pretty print with each hint on its own line
14927            self.write(" /*+ ");
14928            for (i, hint_str) in hint_strings.iter().enumerate() {
14929                if i > 0 {
14930                    self.write_newline();
14931                    self.write("  "); // 2-space indent within hint block
14932                }
14933                self.write(hint_str);
14934            }
14935            self.write(" */");
14936        } else {
14937            // Single line format
14938            self.write(" /*+ ");
14939            let sep = match self.config.dialect {
14940                Some(DialectType::Spark) | Some(DialectType::Databricks) => ", ",
14941                _ => " ",
14942            };
14943            for (i, hint_str) in hint_strings.iter().enumerate() {
14944                if i > 0 {
14945                    self.write(sep);
14946                }
14947                self.write(hint_str);
14948            }
14949            self.write(" */");
14950        }
14951
14952        Ok(())
14953    }
14954
14955    /// Parse raw hint text into individual hint function calls
14956    /// e.g., "LEADING(a b) USE_NL(c)" -> ["LEADING(a b)", "USE_NL(c)"]
14957    /// If the hint contains unparseable content (like SQL keywords), return as single raw string
14958    fn parse_raw_hint_text(&self, text: &str) -> Vec<String> {
14959        let mut results = Vec::new();
14960        let mut chars = text.chars().peekable();
14961        let mut current = String::new();
14962        let mut paren_depth = 0;
14963        let mut has_unparseable_content = false;
14964        let mut position_after_last_function = 0;
14965        let mut char_position = 0;
14966
14967        while let Some(c) = chars.next() {
14968            char_position += c.len_utf8();
14969            match c {
14970                '(' => {
14971                    paren_depth += 1;
14972                    current.push(c);
14973                }
14974                ')' => {
14975                    paren_depth -= 1;
14976                    current.push(c);
14977                    // When we close the outer parenthesis, we've completed a hint function
14978                    if paren_depth == 0 {
14979                        let trimmed = current.trim().to_string();
14980                        if !trimmed.is_empty() {
14981                            // Format this hint for pretty printing if needed
14982                            let formatted = self.format_hint_function(&trimmed);
14983                            results.push(formatted);
14984                        }
14985                        current.clear();
14986                        position_after_last_function = char_position;
14987                    }
14988                }
14989                ' ' | '\t' | '\n' | ',' if paren_depth == 0 => {
14990                    // Space/comma/whitespace outside parentheses - skip
14991                }
14992                _ if paren_depth == 0 => {
14993                    // Character outside parentheses - accumulate for potential hint name
14994                    current.push(c);
14995                }
14996                _ => {
14997                    current.push(c);
14998                }
14999            }
15000        }
15001
15002        // Check if there's remaining text after the last function call
15003        let remaining_text = text[position_after_last_function..].trim();
15004        if !remaining_text.is_empty() {
15005            // Check if it looks like valid hint function names
15006            // Valid hint identifiers typically are uppercase alphanumeric with underscores
15007            // If we see multiple words without parens, it's likely unparseable
15008            let words: Vec<&str> = remaining_text.split_whitespace().collect();
15009            let looks_like_hint_functions = words.iter().all(|word| {
15010                // A valid hint name followed by opening paren, or a standalone uppercase identifier
15011                word.contains('(') || (word.chars().all(|c| c.is_ascii_uppercase() || c == '_'))
15012            });
15013
15014            if !looks_like_hint_functions && words.len() > 1 {
15015                has_unparseable_content = true;
15016            }
15017        }
15018
15019        // If we detected unparseable content (like SQL keywords), return the whole hint as-is
15020        if has_unparseable_content {
15021            return vec![text.trim().to_string()];
15022        }
15023
15024        // If we couldn't parse anything, return the original text as a single hint
15025        if results.is_empty() {
15026            results.push(text.trim().to_string());
15027        }
15028
15029        results
15030    }
15031
15032    /// Format a hint function for pretty printing
15033    /// e.g., "LEADING(aaa bbb ccc ddd)" -> multiline if args are too wide
15034    fn format_hint_function(&self, hint: &str) -> String {
15035        if !self.config.pretty {
15036            return hint.to_string();
15037        }
15038
15039        // Try to parse NAME(args) pattern
15040        if let Some(paren_pos) = hint.find('(') {
15041            if hint.ends_with(')') {
15042                let name = &hint[..paren_pos];
15043                let args_str = &hint[paren_pos + 1..hint.len() - 1];
15044
15045                // Parse arguments (space-separated for Oracle hints)
15046                let args: Vec<&str> = args_str.split_whitespace().collect();
15047
15048                // Calculate total width of arguments
15049                let total_args_width: usize =
15050                    args.iter().map(|s| s.len()).sum::<usize>() + args.len().saturating_sub(1); // spaces between args
15051
15052                // If too wide, format on multiple lines
15053                if total_args_width > self.config.max_text_width && !args.is_empty() {
15054                    let mut result = format!("{}(\n", name);
15055                    for arg in &args {
15056                        result.push_str("    "); // 4-space indent for args
15057                        result.push_str(arg);
15058                        result.push('\n');
15059                    }
15060                    result.push_str("  )"); // 2-space indent for closing paren
15061                    return result;
15062                }
15063            }
15064        }
15065
15066        hint.to_string()
15067    }
15068
15069    /// Convert a hint expression to a string, handling multiline formatting for long arguments
15070    fn hint_expression_to_string(&mut self, expr: &HintExpression) -> Result<String> {
15071        match expr {
15072            HintExpression::Function { name, args } => {
15073                // Generate each argument to a string
15074                let arg_strings: Vec<String> = args
15075                    .iter()
15076                    .map(|arg| {
15077                        let mut gen = Generator::with_config(self.config.clone());
15078                        gen.generate_expression(arg)?;
15079                        Ok(gen.output)
15080                    })
15081                    .collect::<Result<Vec<_>>>()?;
15082
15083                // Oracle hints use space-separated arguments, not comma-separated
15084                let total_args_width: usize = arg_strings.iter().map(|s| s.len()).sum::<usize>()
15085                    + arg_strings.len().saturating_sub(1); // spaces between args
15086
15087                // Check if function args need multiline formatting
15088                // Use too_wide check for argument formatting
15089                let args_multiline =
15090                    self.config.pretty && total_args_width > self.config.max_text_width;
15091
15092                if args_multiline && !arg_strings.is_empty() {
15093                    // Multiline format for long argument lists
15094                    let mut result = format!("{}(\n", name);
15095                    for arg_str in &arg_strings {
15096                        result.push_str("    "); // 4-space indent for args
15097                        result.push_str(arg_str);
15098                        result.push('\n');
15099                    }
15100                    result.push_str("  )"); // 2-space indent for closing paren
15101                    Ok(result)
15102                } else {
15103                    // Single line format with space-separated args (Oracle style)
15104                    let args_str = arg_strings.join(" ");
15105                    Ok(format!("{}({})", name, args_str))
15106                }
15107            }
15108            HintExpression::Identifier(name) => Ok(name.clone()),
15109            HintExpression::Raw(text) => {
15110                // For pretty printing, try to format the raw text
15111                if self.config.pretty {
15112                    Ok(self.format_hint_function(text))
15113                } else {
15114                    Ok(text.clone())
15115                }
15116            }
15117        }
15118    }
15119
15120    fn generate_table(&mut self, table: &TableRef) -> Result<()> {
15121        // PostgreSQL ONLY modifier: prevents scanning child tables
15122        if table.only {
15123            self.write_keyword("ONLY");
15124            self.write_space();
15125        }
15126
15127        // Check for Snowflake IDENTIFIER() function
15128        if let Some(ref identifier_func) = table.identifier_func {
15129            self.generate_expression(identifier_func)?;
15130        } else {
15131            if let Some(catalog) = &table.catalog {
15132                self.generate_identifier(catalog)?;
15133                self.write(".");
15134            }
15135            if let Some(schema) = &table.schema {
15136                self.generate_identifier(schema)?;
15137                self.write(".");
15138            }
15139            self.generate_identifier(&table.name)?;
15140        }
15141
15142        // Output Snowflake CHANGES clause (before partition, includes its own AT/BEFORE/END)
15143        if let Some(changes) = &table.changes {
15144            self.write(" ");
15145            self.generate_changes(changes)?;
15146        }
15147
15148        // Output MySQL PARTITION clause: t1 PARTITION(p0, p1)
15149        if !table.partitions.is_empty() {
15150            self.write_space();
15151            self.write_keyword("PARTITION");
15152            self.write("(");
15153            for (i, partition) in table.partitions.iter().enumerate() {
15154                if i > 0 {
15155                    self.write(", ");
15156                }
15157                self.generate_identifier(partition)?;
15158            }
15159            self.write(")");
15160        }
15161
15162        // Output time travel clause: BEFORE (STATEMENT => ...) or AT (TIMESTAMP => ...)
15163        // Skip if CHANGES clause is present (CHANGES includes its own time travel)
15164        if table.changes.is_none() {
15165            if let Some(when) = &table.when {
15166                self.write_space();
15167                self.generate_historical_data(when)?;
15168            }
15169        }
15170
15171        // Output TSQL FOR SYSTEM_TIME temporal clause
15172        if let Some(ref system_time) = table.system_time {
15173            self.write_space();
15174            self.write(system_time);
15175        }
15176
15177        // Output Presto/Trino time travel: FOR VERSION AS OF / FOR TIMESTAMP AS OF
15178        if let Some(ref version) = table.version {
15179            self.write_space();
15180            self.generate_version(version)?;
15181        }
15182
15183        // When alias_post_tablesample is true, the order is: table TABLESAMPLE (...) alias
15184        // When alias_post_tablesample is false (default), the order is: table alias TABLESAMPLE (...)
15185        // Oracle, Hive, Spark use ALIAS_POST_TABLESAMPLE = true (alias comes after sample)
15186        let alias_post_tablesample = self.config.alias_post_tablesample;
15187
15188        if alias_post_tablesample {
15189            // TABLESAMPLE before alias (Oracle, Hive, Spark)
15190            self.generate_table_sample_clause(table)?;
15191        }
15192
15193        // Output table hints (TSQL: WITH (TABLOCK, INDEX(myindex), ...))
15194        // For SQLite, INDEXED BY hints come after the alias, so skip here
15195        let is_sqlite_hint = matches!(self.config.dialect, Some(DialectType::SQLite))
15196            && table.hints.iter().any(|h| {
15197                if let Expression::Identifier(id) = h {
15198                    id.name.starts_with("INDEXED BY") || id.name == "NOT INDEXED"
15199                } else {
15200                    false
15201                }
15202            });
15203        if !table.hints.is_empty() && !is_sqlite_hint {
15204            for hint in &table.hints {
15205                self.write_space();
15206                self.generate_expression(hint)?;
15207            }
15208        }
15209
15210        if let Some(alias) = &table.alias {
15211            self.write_space();
15212            // Output AS if it was explicitly present in the input, OR for certain dialects/cases
15213            // Generic mode and most dialects always use AS for table aliases
15214            let always_use_as = self.config.dialect.is_none()
15215                || matches!(
15216                    self.config.dialect,
15217                    Some(DialectType::Generic)
15218                        | Some(DialectType::PostgreSQL)
15219                        | Some(DialectType::Redshift)
15220                        | Some(DialectType::Snowflake)
15221                        | Some(DialectType::BigQuery)
15222                        | Some(DialectType::Presto)
15223                        | Some(DialectType::Trino)
15224                        | Some(DialectType::TSQL)
15225                        | Some(DialectType::Fabric)
15226                        | Some(DialectType::MySQL)
15227                        | Some(DialectType::Spark)
15228                        | Some(DialectType::Hive)
15229                        | Some(DialectType::SQLite)
15230                        | Some(DialectType::Drill)
15231                );
15232            let is_stage_ref = table.name.name.starts_with('@');
15233            // Oracle never uses AS for table aliases
15234            let suppress_as = matches!(self.config.dialect, Some(DialectType::Oracle));
15235            if !suppress_as && (table.alias_explicit_as || always_use_as || is_stage_ref) {
15236                self.write_keyword("AS");
15237                self.write_space();
15238            }
15239            self.generate_identifier(alias)?;
15240
15241            // Output column aliases if present: AS t(c1, c2)
15242            // Skip for dialects that don't support table alias columns (BigQuery, SQLite)
15243            if !table.column_aliases.is_empty() && self.config.supports_table_alias_columns {
15244                self.write("(");
15245                for (i, col_alias) in table.column_aliases.iter().enumerate() {
15246                    if i > 0 {
15247                        self.write(", ");
15248                    }
15249                    self.generate_identifier(col_alias)?;
15250                }
15251                self.write(")");
15252            }
15253        }
15254
15255        // For default behavior (alias_post_tablesample = false), output TABLESAMPLE after alias
15256        if !alias_post_tablesample {
15257            self.generate_table_sample_clause(table)?;
15258        }
15259
15260        // Output SQLite INDEXED BY / NOT INDEXED hints after alias
15261        if is_sqlite_hint {
15262            for hint in &table.hints {
15263                self.write_space();
15264                self.generate_expression(hint)?;
15265            }
15266        }
15267
15268        // ClickHouse FINAL modifier
15269        if table.final_ && matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
15270            self.write_space();
15271            self.write_keyword("FINAL");
15272        }
15273
15274        // Output trailing comments
15275        for comment in &table.trailing_comments {
15276            self.write_space();
15277            self.write_formatted_comment(comment);
15278        }
15279
15280        Ok(())
15281    }
15282
15283    /// Helper to output TABLESAMPLE clause for a table reference
15284    fn generate_table_sample_clause(&mut self, table: &TableRef) -> Result<()> {
15285        if let Some(ref ts) = table.table_sample {
15286            self.write_space();
15287            if ts.is_using_sample {
15288                self.write_keyword("USING SAMPLE");
15289            } else {
15290                // Use the configured tablesample keyword (e.g., "TABLESAMPLE" or "SAMPLE")
15291                self.write_keyword(self.config.tablesample_keywords);
15292            }
15293            self.generate_sample_body(ts)?;
15294            // Seed for table-level sample - use dialect's configured keyword
15295            if let Some(ref seed) = ts.seed {
15296                self.write_space();
15297                self.write_keyword(self.config.tablesample_seed_keyword);
15298                self.write(" (");
15299                self.generate_expression(seed)?;
15300                self.write(")");
15301            }
15302        }
15303        Ok(())
15304    }
15305
15306    fn generate_stage_reference(&mut self, sr: &StageReference) -> Result<()> {
15307        // Output: '@stage_name/path' if quoted, or @stage_name/path otherwise
15308        // Optionally followed by (FILE_FORMAT => 'fmt', PATTERN => '*.csv')
15309
15310        if sr.quoted {
15311            self.write("'");
15312        }
15313
15314        self.write(&sr.name);
15315        if let Some(path) = &sr.path {
15316            self.write(path);
15317        }
15318
15319        if sr.quoted {
15320            self.write("'");
15321        }
15322
15323        // Output FILE_FORMAT and PATTERN if present
15324        let has_options = sr.file_format.is_some() || sr.pattern.is_some();
15325        if has_options {
15326            self.write(" (");
15327            let mut first = true;
15328
15329            if let Some(file_format) = &sr.file_format {
15330                if !first {
15331                    self.write(", ");
15332                }
15333                self.write_keyword("FILE_FORMAT");
15334                self.write(" => ");
15335                self.generate_expression(file_format)?;
15336                first = false;
15337            }
15338
15339            if let Some(pattern) = &sr.pattern {
15340                if !first {
15341                    self.write(", ");
15342                }
15343                self.write_keyword("PATTERN");
15344                self.write(" => '");
15345                self.write(pattern);
15346                self.write("'");
15347            }
15348
15349            self.write(")");
15350        }
15351        Ok(())
15352    }
15353
15354    fn generate_star(&mut self, star: &Star) -> Result<()> {
15355        use crate::dialects::DialectType;
15356
15357        if let Some(table) = &star.table {
15358            self.generate_identifier(table)?;
15359            self.write(".");
15360        }
15361        self.write("*");
15362
15363        // Generate EXCLUDE/EXCEPT clause based on dialect
15364        if let Some(except) = &star.except {
15365            if !except.is_empty() {
15366                self.write_space();
15367                // Use dialect-appropriate keyword
15368                match self.config.dialect {
15369                    Some(DialectType::BigQuery) => self.write_keyword("EXCEPT"),
15370                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => {
15371                        self.write_keyword("EXCLUDE")
15372                    }
15373                    _ => self.write_keyword("EXCEPT"), // Default to EXCEPT
15374                }
15375                self.write(" (");
15376                for (i, col) in except.iter().enumerate() {
15377                    if i > 0 {
15378                        self.write(", ");
15379                    }
15380                    self.generate_identifier(col)?;
15381                }
15382                self.write(")");
15383            }
15384        }
15385
15386        // Generate REPLACE clause
15387        if let Some(replace) = &star.replace {
15388            if !replace.is_empty() {
15389                self.write_space();
15390                self.write_keyword("REPLACE");
15391                self.write(" (");
15392                for (i, alias) in replace.iter().enumerate() {
15393                    if i > 0 {
15394                        self.write(", ");
15395                    }
15396                    self.generate_expression(&alias.this)?;
15397                    self.write_space();
15398                    self.write_keyword("AS");
15399                    self.write_space();
15400                    self.generate_identifier(&alias.alias)?;
15401                }
15402                self.write(")");
15403            }
15404        }
15405
15406        // Generate RENAME clause (Snowflake specific)
15407        if let Some(rename) = &star.rename {
15408            if !rename.is_empty() {
15409                self.write_space();
15410                self.write_keyword("RENAME");
15411                self.write(" (");
15412                for (i, (old_name, new_name)) in rename.iter().enumerate() {
15413                    if i > 0 {
15414                        self.write(", ");
15415                    }
15416                    self.generate_identifier(old_name)?;
15417                    self.write_space();
15418                    self.write_keyword("AS");
15419                    self.write_space();
15420                    self.generate_identifier(new_name)?;
15421                }
15422                self.write(")");
15423            }
15424        }
15425
15426        // Output trailing comments
15427        for comment in &star.trailing_comments {
15428            self.write_space();
15429            self.write_formatted_comment(comment);
15430        }
15431
15432        Ok(())
15433    }
15434
15435    /// Generate Snowflake braced wildcard syntax: {*}, {tbl.*}, {* EXCLUDE (...)}, {* ILIKE '...'}
15436    fn generate_braced_wildcard(&mut self, expr: &Expression) -> Result<()> {
15437        self.write("{");
15438        match expr {
15439            Expression::Star(star) => {
15440                // Generate the star (table.* or just * with optional EXCLUDE)
15441                self.generate_star(star)?;
15442            }
15443            Expression::ILike(ilike) => {
15444                // {* ILIKE 'pattern'} syntax
15445                self.generate_expression(&ilike.left)?;
15446                self.write_space();
15447                self.write_keyword("ILIKE");
15448                self.write_space();
15449                self.generate_expression(&ilike.right)?;
15450            }
15451            _ => {
15452                self.generate_expression(expr)?;
15453            }
15454        }
15455        self.write("}");
15456        Ok(())
15457    }
15458
15459    fn generate_alias(&mut self, alias: &Alias) -> Result<()> {
15460        // Generate inner expression, but skip trailing comments if they're in pre_alias_comments
15461        // to avoid duplication (comments are captured as both Column.trailing_comments
15462        // and Alias.pre_alias_comments during parsing)
15463        match &alias.this {
15464            Expression::Column(col) => {
15465                // Generate column without trailing comments - they're in pre_alias_comments
15466                if let Some(table) = &col.table {
15467                    self.generate_identifier(table)?;
15468                    self.write(".");
15469                }
15470                self.generate_identifier(&col.name)?;
15471            }
15472            _ => {
15473                self.generate_expression(&alias.this)?;
15474            }
15475        }
15476
15477        // Handle pre-alias comments: when there are no trailing_comments, sqlglot
15478        // moves pre-alias comments to after the alias. When there are also trailing_comments,
15479        // keep pre-alias comments in their original position (between expression and AS).
15480        if !alias.pre_alias_comments.is_empty() && !alias.trailing_comments.is_empty() {
15481            for comment in &alias.pre_alias_comments {
15482                self.write_space();
15483                self.write_formatted_comment(comment);
15484            }
15485        }
15486
15487        use crate::dialects::DialectType;
15488
15489        // Determine if we should skip AS keyword for table-valued function aliases
15490        // Oracle and some other dialects don't use AS for table aliases
15491        // Note: We specifically use TableFromRows here, NOT Function, because Function
15492        // matches regular functions like MATCH_NUMBER() which should include the AS keyword.
15493        // TableFromRows represents TABLE(expr) constructs which are actual table-valued functions.
15494        let is_table_source = matches!(
15495            &alias.this,
15496            Expression::JSONTable(_)
15497                | Expression::XMLTable(_)
15498                | Expression::TableFromRows(_)
15499                | Expression::Unnest(_)
15500                | Expression::MatchRecognize(_)
15501                | Expression::Select(_)
15502                | Expression::Subquery(_)
15503                | Expression::Paren(_)
15504        );
15505        let dialect_skips_table_alias_as = matches!(self.config.dialect, Some(DialectType::Oracle));
15506        let skip_as = is_table_source && dialect_skips_table_alias_as;
15507
15508        self.write_space();
15509        if !skip_as {
15510            self.write_keyword("AS");
15511            self.write_space();
15512        }
15513
15514        // BigQuery doesn't support column aliases in table aliases: AS t(c1, c2)
15515        let skip_column_aliases = matches!(self.config.dialect, Some(DialectType::BigQuery));
15516
15517        // Check if we have column aliases only (no table alias name)
15518        if alias.alias.is_empty() && !alias.column_aliases.is_empty() && !skip_column_aliases {
15519            // Generate AS (col1, col2, ...)
15520            self.write("(");
15521            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
15522                if i > 0 {
15523                    self.write(", ");
15524                }
15525                self.generate_alias_identifier(col_alias)?;
15526            }
15527            self.write(")");
15528        } else if !alias.column_aliases.is_empty() && !skip_column_aliases {
15529            // Generate AS alias(col1, col2, ...)
15530            self.generate_alias_identifier(&alias.alias)?;
15531            self.write("(");
15532            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
15533                if i > 0 {
15534                    self.write(", ");
15535                }
15536                self.generate_alias_identifier(col_alias)?;
15537            }
15538            self.write(")");
15539        } else {
15540            // Simple alias (or BigQuery without column aliases)
15541            self.generate_alias_identifier(&alias.alias)?;
15542        }
15543
15544        // Output trailing comments (comments after the alias)
15545        for comment in &alias.trailing_comments {
15546            self.write_space();
15547            self.write_formatted_comment(comment);
15548        }
15549
15550        // Output pre-alias comments: when there are no trailing_comments, sqlglot
15551        // moves pre-alias comments to after the alias. When there are trailing_comments,
15552        // the pre-alias comments were already lost (consumed as column trailing comments
15553        // that were then used as pre_alias_comments). We always emit them after alias.
15554        if alias.trailing_comments.is_empty() {
15555            for comment in &alias.pre_alias_comments {
15556                self.write_space();
15557                self.write_formatted_comment(comment);
15558            }
15559        }
15560
15561        Ok(())
15562    }
15563
15564    fn generate_cast(&mut self, cast: &Cast) -> Result<()> {
15565        use crate::dialects::DialectType;
15566
15567        // SingleStore uses :> syntax
15568        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
15569            self.generate_expression(&cast.this)?;
15570            self.write(" :> ");
15571            self.generate_data_type(&cast.to)?;
15572            return Ok(());
15573        }
15574
15575        // Teradata: CAST(x AS FORMAT 'fmt') (no data type)
15576        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
15577            let is_unknown_type = matches!(cast.to, DataType::Unknown)
15578                || matches!(cast.to, DataType::Custom { ref name } if name.is_empty());
15579            if is_unknown_type {
15580                if let Some(format) = &cast.format {
15581                    self.write_keyword("CAST");
15582                    self.write("(");
15583                    self.generate_expression(&cast.this)?;
15584                    self.write_space();
15585                    self.write_keyword("AS");
15586                    self.write_space();
15587                    self.write_keyword("FORMAT");
15588                    self.write_space();
15589                    self.generate_expression(format)?;
15590                    self.write(")");
15591                    return Ok(());
15592                }
15593            }
15594        }
15595
15596        // Oracle: CAST(x AS DATE/TIMESTAMP ..., 'format') -> TO_DATE/TO_TIMESTAMP(x, 'format')
15597        // This follows Python sqlglot's behavior of transforming CAST with format to native functions
15598        if matches!(self.config.dialect, Some(DialectType::Oracle)) {
15599            if let Some(format) = &cast.format {
15600                // Check if target type is DATE or TIMESTAMP
15601                let is_date = matches!(cast.to, DataType::Date);
15602                let is_timestamp = matches!(cast.to, DataType::Timestamp { .. });
15603
15604                if is_date || is_timestamp {
15605                    let func_name = if is_date { "TO_DATE" } else { "TO_TIMESTAMP" };
15606                    self.write_keyword(func_name);
15607                    self.write("(");
15608                    self.generate_expression(&cast.this)?;
15609                    self.write(", ");
15610
15611                    // Normalize format string for Oracle (HH -> HH12)
15612                    // Oracle HH is 12-hour format, same as HH12. For clarity, Python sqlglot uses HH12.
15613                    if let Expression::Literal(Literal::String(fmt_str)) = format.as_ref() {
15614                        let normalized = self.normalize_oracle_format(fmt_str);
15615                        self.write("'");
15616                        self.write(&normalized);
15617                        self.write("'");
15618                    } else {
15619                        self.generate_expression(format)?;
15620                    }
15621
15622                    self.write(")");
15623                    return Ok(());
15624                }
15625            }
15626        }
15627
15628        // BigQuery: CAST(ARRAY[...] AS ARRAY<T>) -> ARRAY<T>[...]
15629        // This preserves sqlglot's typed inline array literal output.
15630        if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
15631            if let Expression::Array(arr) = &cast.this {
15632                self.generate_data_type(&cast.to)?;
15633                // Output just the bracket content [values] without the ARRAY prefix
15634                self.write("[");
15635                for (i, expr) in arr.expressions.iter().enumerate() {
15636                    if i > 0 {
15637                        self.write(", ");
15638                    }
15639                    self.generate_expression(expr)?;
15640                }
15641                self.write("]");
15642                return Ok(());
15643            }
15644            if matches!(&cast.this, Expression::ArrayFunc(_)) {
15645                self.generate_data_type(&cast.to)?;
15646                self.generate_expression(&cast.this)?;
15647                return Ok(());
15648            }
15649        }
15650
15651        // DuckDB/Presto/Trino: When CAST(Struct([unnamed]) AS STRUCT(...)),
15652        // convert the inner Struct to ROW(values...) format
15653        if matches!(
15654            self.config.dialect,
15655            Some(DialectType::DuckDB) | Some(DialectType::Presto) | Some(DialectType::Trino)
15656        ) {
15657            if let Expression::Struct(ref s) = cast.this {
15658                let all_unnamed = s.fields.iter().all(|(name, _)| name.is_none());
15659                if all_unnamed && matches!(cast.to, DataType::Struct { .. }) {
15660                    self.write_keyword("CAST");
15661                    self.write("(");
15662                    self.generate_struct_as_row(s)?;
15663                    self.write_space();
15664                    self.write_keyword("AS");
15665                    self.write_space();
15666                    self.generate_data_type(&cast.to)?;
15667                    self.write(")");
15668                    return Ok(());
15669                }
15670            }
15671        }
15672
15673        // Determine if we should use :: syntax based on dialect
15674        // PostgreSQL prefers :: for identity, most others prefer CAST()
15675        let use_double_colon = cast.double_colon_syntax && self.dialect_prefers_double_colon();
15676
15677        if use_double_colon {
15678            // PostgreSQL :: syntax: expr::type
15679            self.generate_expression(&cast.this)?;
15680            self.write("::");
15681            self.generate_data_type(&cast.to)?;
15682        } else {
15683            // Standard CAST() syntax
15684            self.write_keyword("CAST");
15685            self.write("(");
15686            self.generate_expression(&cast.this)?;
15687            self.write_space();
15688            self.write_keyword("AS");
15689            self.write_space();
15690            // For MySQL/SingleStore/TiDB, map text/blob variant types to CHAR in CAST
15691            // This matches Python sqlglot's CAST_MAPPING behavior
15692            if matches!(
15693                self.config.dialect,
15694                Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB)
15695            ) {
15696                match &cast.to {
15697                    DataType::Custom { ref name } => {
15698                        let upper = name.to_uppercase();
15699                        match upper.as_str() {
15700                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" | "LONGBLOB" | "MEDIUMBLOB"
15701                            | "TINYBLOB" => {
15702                                self.write_keyword("CHAR");
15703                            }
15704                            _ => {
15705                                self.generate_data_type(&cast.to)?;
15706                            }
15707                        }
15708                    }
15709                    DataType::VarChar { length, .. } => {
15710                        // MySQL CAST: VARCHAR -> CHAR
15711                        self.write_keyword("CHAR");
15712                        if let Some(n) = length {
15713                            self.write(&format!("({})", n));
15714                        }
15715                    }
15716                    DataType::Text => {
15717                        // MySQL CAST: TEXT -> CHAR
15718                        self.write_keyword("CHAR");
15719                    }
15720                    DataType::Timestamp {
15721                        precision,
15722                        timezone: false,
15723                    } => {
15724                        // MySQL CAST: TIMESTAMP -> DATETIME
15725                        self.write_keyword("DATETIME");
15726                        if let Some(p) = precision {
15727                            self.write(&format!("({})", p));
15728                        }
15729                    }
15730                    _ => {
15731                        self.generate_data_type(&cast.to)?;
15732                    }
15733                }
15734            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
15735                // Snowflake CAST: STRING -> VARCHAR
15736                match &cast.to {
15737                    DataType::String { length } => {
15738                        self.write_keyword("VARCHAR");
15739                        if let Some(n) = length {
15740                            self.write(&format!("({})", n));
15741                        }
15742                    }
15743                    _ => {
15744                        self.generate_data_type(&cast.to)?;
15745                    }
15746                }
15747            } else {
15748                self.generate_data_type(&cast.to)?;
15749            }
15750
15751            // Output DEFAULT ... ON CONVERSION ERROR clause if present (Oracle)
15752            if let Some(default) = &cast.default {
15753                self.write_space();
15754                self.write_keyword("DEFAULT");
15755                self.write_space();
15756                self.generate_expression(default)?;
15757                self.write_space();
15758                self.write_keyword("ON");
15759                self.write_space();
15760                self.write_keyword("CONVERSION");
15761                self.write_space();
15762                self.write_keyword("ERROR");
15763            }
15764
15765            // Output FORMAT clause if present (BigQuery: CAST(x AS STRING FORMAT 'format'))
15766            // For Oracle with comma-separated format: CAST(x AS DATE DEFAULT NULL ON CONVERSION ERROR, 'format')
15767            if let Some(format) = &cast.format {
15768                // Check if Oracle dialect - use comma syntax
15769                if matches!(
15770                    self.config.dialect,
15771                    Some(crate::dialects::DialectType::Oracle)
15772                ) {
15773                    self.write(", ");
15774                } else {
15775                    self.write_space();
15776                    self.write_keyword("FORMAT");
15777                    self.write_space();
15778                }
15779                self.generate_expression(format)?;
15780            }
15781
15782            self.write(")");
15783            // Output trailing comments
15784            for comment in &cast.trailing_comments {
15785                self.write_space();
15786                self.write_formatted_comment(comment);
15787            }
15788        }
15789        Ok(())
15790    }
15791
15792    /// Generate a Struct as ROW(values...) format, recursively converting inner Struct to ROW too.
15793    /// Used for DuckDB/Presto/Trino CAST(Struct AS STRUCT(...)) context.
15794    fn generate_struct_as_row(&mut self, s: &crate::expressions::Struct) -> Result<()> {
15795        self.write_keyword("ROW");
15796        self.write("(");
15797        for (i, (_, expr)) in s.fields.iter().enumerate() {
15798            if i > 0 {
15799                self.write(", ");
15800            }
15801            // Recursively convert inner Struct to ROW format
15802            if let Expression::Struct(ref inner_s) = expr {
15803                self.generate_struct_as_row(inner_s)?;
15804            } else {
15805                self.generate_expression(expr)?;
15806            }
15807        }
15808        self.write(")");
15809        Ok(())
15810    }
15811
15812    /// Normalize Oracle date/time format strings
15813    /// HH -> HH12 (both are 12-hour format, but Python sqlglot prefers explicit HH12)
15814    fn normalize_oracle_format(&self, format: &str) -> String {
15815        // Replace standalone HH with HH12 (but not HH12 or HH24)
15816        // We need to be careful not to replace HH12 -> HH1212 or HH24 -> HH1224
15817        let mut result = String::new();
15818        let chars: Vec<char> = format.chars().collect();
15819        let mut i = 0;
15820
15821        while i < chars.len() {
15822            if i + 1 < chars.len() && chars[i] == 'H' && chars[i + 1] == 'H' {
15823                // Check what follows HH
15824                if i + 2 < chars.len() {
15825                    let next = chars[i + 2];
15826                    if next == '1' || next == '2' {
15827                        // This is HH12 or HH24, keep as is
15828                        result.push('H');
15829                        result.push('H');
15830                        i += 2;
15831                        continue;
15832                    }
15833                }
15834                // Standalone HH -> HH12
15835                result.push_str("HH12");
15836                i += 2;
15837            } else {
15838                result.push(chars[i]);
15839                i += 1;
15840            }
15841        }
15842
15843        result
15844    }
15845
15846    /// Check if the current dialect prefers :: cast syntax
15847    /// Note: Python sqlglot normalizes all :: to CAST() for output, even for PostgreSQL
15848    /// So we return false for all dialects to match Python sqlglot's behavior
15849    fn dialect_prefers_double_colon(&self) -> bool {
15850        // Python sqlglot normalizes :: syntax to CAST() for all dialects
15851        // Even PostgreSQL outputs CAST() not ::
15852        false
15853    }
15854
15855    /// Generate MOD function - uses % operator for Snowflake/MySQL/Presto/Trino, MOD() for others
15856    fn generate_mod_func(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
15857        use crate::dialects::DialectType;
15858
15859        // Snowflake, MySQL, Presto, Trino, PostgreSQL, and DuckDB prefer x % y instead of MOD(x, y)
15860        let use_percent_operator = matches!(
15861            self.config.dialect,
15862            Some(DialectType::Snowflake)
15863                | Some(DialectType::MySQL)
15864                | Some(DialectType::Presto)
15865                | Some(DialectType::Trino)
15866                | Some(DialectType::PostgreSQL)
15867                | Some(DialectType::DuckDB)
15868                | Some(DialectType::Hive)
15869                | Some(DialectType::Spark)
15870                | Some(DialectType::Databricks)
15871                | Some(DialectType::Athena)
15872        );
15873
15874        if use_percent_operator {
15875            // Wrap complex expressions in parens to preserve precedence
15876            // Since % has higher precedence than +/-, we need parens for Add/Sub on either side
15877            let needs_paren = |e: &Expression| matches!(e, Expression::Add(_) | Expression::Sub(_));
15878            if needs_paren(&f.this) {
15879                self.write("(");
15880                self.generate_expression(&f.this)?;
15881                self.write(")");
15882            } else {
15883                self.generate_expression(&f.this)?;
15884            }
15885            self.write(" % ");
15886            if needs_paren(&f.expression) {
15887                self.write("(");
15888                self.generate_expression(&f.expression)?;
15889                self.write(")");
15890            } else {
15891                self.generate_expression(&f.expression)?;
15892            }
15893            Ok(())
15894        } else {
15895            self.generate_binary_func("MOD", &f.this, &f.expression)
15896        }
15897    }
15898
15899    /// Generate IFNULL - uses COALESCE for Snowflake, IFNULL for others
15900    fn generate_ifnull(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
15901        use crate::dialects::DialectType;
15902
15903        // Snowflake normalizes IFNULL to COALESCE
15904        let func_name = match self.config.dialect {
15905            Some(DialectType::Snowflake) => "COALESCE",
15906            _ => "IFNULL",
15907        };
15908
15909        self.generate_binary_func(func_name, &f.this, &f.expression)
15910    }
15911
15912    /// Generate NVL - preserves original name if available, otherwise uses dialect-specific output
15913    fn generate_nvl(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
15914        // Use original function name if preserved (for identity tests)
15915        if let Some(ref original_name) = f.original_name {
15916            return self.generate_binary_func(original_name, &f.this, &f.expression);
15917        }
15918
15919        // Otherwise, use dialect-specific function names
15920        use crate::dialects::DialectType;
15921        let func_name = match self.config.dialect {
15922            Some(DialectType::Snowflake)
15923            | Some(DialectType::ClickHouse)
15924            | Some(DialectType::PostgreSQL)
15925            | Some(DialectType::Presto)
15926            | Some(DialectType::Trino)
15927            | Some(DialectType::Athena)
15928            | Some(DialectType::DuckDB)
15929            | Some(DialectType::BigQuery)
15930            | Some(DialectType::Spark)
15931            | Some(DialectType::Databricks)
15932            | Some(DialectType::Hive) => "COALESCE",
15933            Some(DialectType::MySQL)
15934            | Some(DialectType::Doris)
15935            | Some(DialectType::StarRocks)
15936            | Some(DialectType::SingleStore)
15937            | Some(DialectType::TiDB) => "IFNULL",
15938            _ => "NVL",
15939        };
15940
15941        self.generate_binary_func(func_name, &f.this, &f.expression)
15942    }
15943
15944    /// Generate STDDEV_SAMP - uses STDDEV for Snowflake, STDDEV_SAMP for others
15945    fn generate_stddev_samp(&mut self, f: &crate::expressions::AggFunc) -> Result<()> {
15946        use crate::dialects::DialectType;
15947
15948        // Snowflake normalizes STDDEV_SAMP to STDDEV
15949        let func_name = match self.config.dialect {
15950            Some(DialectType::Snowflake) => "STDDEV",
15951            _ => "STDDEV_SAMP",
15952        };
15953
15954        self.generate_agg_func(func_name, f)
15955    }
15956
15957    fn generate_collation(&mut self, coll: &CollationExpr) -> Result<()> {
15958        self.generate_expression(&coll.this)?;
15959        self.write_space();
15960        self.write_keyword("COLLATE");
15961        self.write_space();
15962        if coll.quoted {
15963            // Single-quoted string: COLLATE 'de_DE'
15964            self.write("'");
15965            self.write(&coll.collation);
15966            self.write("'");
15967        } else if coll.double_quoted {
15968            // Double-quoted identifier: COLLATE "de_DE"
15969            self.write("\"");
15970            self.write(&coll.collation);
15971            self.write("\"");
15972        } else {
15973            // Unquoted identifier: COLLATE de_DE
15974            self.write(&coll.collation);
15975        }
15976        Ok(())
15977    }
15978
15979    fn generate_case(&mut self, case: &Case) -> Result<()> {
15980        // In pretty mode, decide whether to expand based on total text width
15981        let multiline_case = if self.config.pretty {
15982            // Build the flat representation to check width
15983            let mut statements: Vec<String> = Vec::new();
15984            let operand_str = if let Some(operand) = &case.operand {
15985                let s = self.generate_to_string(operand)?;
15986                statements.push(format!("CASE {}", s));
15987                s
15988            } else {
15989                statements.push("CASE".to_string());
15990                String::new()
15991            };
15992            let _ = operand_str;
15993            for (condition, result) in &case.whens {
15994                statements.push(format!("WHEN {}", self.generate_to_string(condition)?));
15995                statements.push(format!("THEN {}", self.generate_to_string(result)?));
15996            }
15997            if let Some(else_) = &case.else_ {
15998                statements.push(format!("ELSE {}", self.generate_to_string(else_)?));
15999            }
16000            statements.push("END".to_string());
16001            self.too_wide(&statements)
16002        } else {
16003            false
16004        };
16005
16006        self.write_keyword("CASE");
16007        if let Some(operand) = &case.operand {
16008            self.write_space();
16009            self.generate_expression(operand)?;
16010        }
16011        if multiline_case {
16012            self.indent_level += 1;
16013        }
16014        for (condition, result) in &case.whens {
16015            if multiline_case {
16016                self.write_newline();
16017                self.write_indent();
16018            } else {
16019                self.write_space();
16020            }
16021            self.write_keyword("WHEN");
16022            self.write_space();
16023            self.generate_expression(condition)?;
16024            if multiline_case {
16025                self.write_newline();
16026                self.write_indent();
16027            } else {
16028                self.write_space();
16029            }
16030            self.write_keyword("THEN");
16031            self.write_space();
16032            self.generate_expression(result)?;
16033        }
16034        if let Some(else_) = &case.else_ {
16035            if multiline_case {
16036                self.write_newline();
16037                self.write_indent();
16038            } else {
16039                self.write_space();
16040            }
16041            self.write_keyword("ELSE");
16042            self.write_space();
16043            self.generate_expression(else_)?;
16044        }
16045        if multiline_case {
16046            self.indent_level -= 1;
16047            self.write_newline();
16048            self.write_indent();
16049        } else {
16050            self.write_space();
16051        }
16052        self.write_keyword("END");
16053        // Emit any comments that were attached to the CASE keyword
16054        for comment in &case.comments {
16055            self.write(" ");
16056            self.write_formatted_comment(comment);
16057        }
16058        Ok(())
16059    }
16060
16061    fn generate_function(&mut self, func: &Function) -> Result<()> {
16062        // Normalize function name based on dialect settings
16063        let normalized_name = self.normalize_func_name(&func.name);
16064        let upper_name = func.name.to_uppercase();
16065
16066        // DuckDB: ARRAY_CONSTRUCT_COMPACT(a, b, c) -> LIST_FILTER([a, b, c], _u -> NOT _u IS NULL)
16067        if matches!(self.config.dialect, Some(DialectType::DuckDB))
16068            && upper_name == "ARRAY_CONSTRUCT_COMPACT"
16069        {
16070            self.write("LIST_FILTER(");
16071            self.write("[");
16072            for (i, arg) in func.args.iter().enumerate() {
16073                if i > 0 {
16074                    self.write(", ");
16075                }
16076                self.generate_expression(arg)?;
16077            }
16078            self.write("], _u -> NOT _u IS NULL)");
16079            return Ok(());
16080        }
16081
16082        // STRUCT function: BigQuery STRUCT('Alice' AS name, 85 AS score) -> dialect-specific
16083        if upper_name == "STRUCT"
16084            && !matches!(
16085                self.config.dialect,
16086                Some(DialectType::BigQuery)
16087                    | Some(DialectType::Spark)
16088                    | Some(DialectType::Databricks)
16089                    | Some(DialectType::Hive)
16090                    | None
16091            )
16092        {
16093            return self.generate_struct_function_cross_dialect(func);
16094        }
16095
16096        // SingleStore: __SS_JSON_PATH_QMARK__(expr, key) -> expr::?key
16097        // This is an internal marker function for ::? JSON path syntax
16098        if upper_name == "__SS_JSON_PATH_QMARK__" && func.args.len() == 2 {
16099            self.generate_expression(&func.args[0])?;
16100            self.write("::?");
16101            // Extract the key from the string literal
16102            if let Expression::Literal(crate::expressions::Literal::String(key)) = &func.args[1] {
16103                self.write(key);
16104            } else {
16105                self.generate_expression(&func.args[1])?;
16106            }
16107            return Ok(());
16108        }
16109
16110        // PostgreSQL: __PG_BITWISE_XOR__(a, b) -> a # b
16111        if upper_name == "__PG_BITWISE_XOR__" && func.args.len() == 2 {
16112            self.generate_expression(&func.args[0])?;
16113            self.write(" # ");
16114            self.generate_expression(&func.args[1])?;
16115            return Ok(());
16116        }
16117
16118        // Spark/Hive family: unwrap TRY(expr) since these dialects don't emit TRY as a scalar wrapper.
16119        if matches!(
16120            self.config.dialect,
16121            Some(DialectType::Spark | DialectType::Databricks | DialectType::Hive)
16122        ) && upper_name == "TRY"
16123            && func.args.len() == 1
16124        {
16125            self.generate_expression(&func.args[0])?;
16126            return Ok(());
16127        }
16128
16129        // ClickHouse normalization: toStartOfDay(x) -> dateTrunc('DAY', x)
16130        if self.config.dialect == Some(DialectType::ClickHouse)
16131            && upper_name == "TOSTARTOFDAY"
16132            && func.args.len() == 1
16133        {
16134            self.write("dateTrunc('DAY', ");
16135            self.generate_expression(&func.args[0])?;
16136            self.write(")");
16137            return Ok(());
16138        }
16139
16140        // Redshift: CONCAT(a, b, ...) -> a || b || ...
16141        if self.config.dialect == Some(DialectType::Redshift)
16142            && upper_name == "CONCAT"
16143            && func.args.len() >= 2
16144        {
16145            for (i, arg) in func.args.iter().enumerate() {
16146                if i > 0 {
16147                    self.write(" || ");
16148                }
16149                self.generate_expression(arg)?;
16150            }
16151            return Ok(());
16152        }
16153
16154        // Redshift: CONCAT_WS(delim, a, b, c) -> a || delim || b || delim || c
16155        if self.config.dialect == Some(DialectType::Redshift)
16156            && upper_name == "CONCAT_WS"
16157            && func.args.len() >= 2
16158        {
16159            let sep = &func.args[0];
16160            for (i, arg) in func.args.iter().skip(1).enumerate() {
16161                if i > 0 {
16162                    self.write(" || ");
16163                    self.generate_expression(sep)?;
16164                    self.write(" || ");
16165                }
16166                self.generate_expression(arg)?;
16167            }
16168            return Ok(());
16169        }
16170
16171        // Redshift: DATEDIFF/DATE_DIFF(unit, start, end) -> DATEDIFF(UNIT, start, end)
16172        // Unit should be unquoted uppercase identifier
16173        if self.config.dialect == Some(DialectType::Redshift)
16174            && (upper_name == "DATEDIFF" || upper_name == "DATE_DIFF")
16175            && func.args.len() == 3
16176        {
16177            self.write_keyword("DATEDIFF");
16178            self.write("(");
16179            // First arg is unit - normalize to unquoted uppercase
16180            self.write_redshift_date_part(&func.args[0]);
16181            self.write(", ");
16182            self.generate_expression(&func.args[1])?;
16183            self.write(", ");
16184            self.generate_expression(&func.args[2])?;
16185            self.write(")");
16186            return Ok(());
16187        }
16188
16189        // Redshift: DATEADD/DATE_ADD(unit, interval, date) -> DATEADD(UNIT, interval, date)
16190        // Unit should be unquoted uppercase identifier
16191        if self.config.dialect == Some(DialectType::Redshift)
16192            && (upper_name == "DATEADD" || upper_name == "DATE_ADD")
16193            && func.args.len() == 3
16194        {
16195            self.write_keyword("DATEADD");
16196            self.write("(");
16197            // First arg is unit - normalize to unquoted uppercase
16198            self.write_redshift_date_part(&func.args[0]);
16199            self.write(", ");
16200            self.generate_expression(&func.args[1])?;
16201            self.write(", ");
16202            self.generate_expression(&func.args[2])?;
16203            self.write(")");
16204            return Ok(());
16205        }
16206
16207        // UUID_STRING(args) from Snowflake -> dialect-specific UUID function (dropping args)
16208        if upper_name == "UUID_STRING"
16209            && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None)
16210        {
16211            let func_name = match self.config.dialect {
16212                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
16213                Some(DialectType::BigQuery) => "GENERATE_UUID",
16214                _ => "UUID",
16215            };
16216            self.write_keyword(func_name);
16217            self.write("()");
16218            return Ok(());
16219        }
16220
16221        // Redshift: DATE_TRUNC('unit', date) -> DATE_TRUNC('UNIT', date)
16222        // Unit should be quoted uppercase string
16223        if self.config.dialect == Some(DialectType::Redshift)
16224            && upper_name == "DATE_TRUNC"
16225            && func.args.len() == 2
16226        {
16227            self.write_keyword("DATE_TRUNC");
16228            self.write("(");
16229            // First arg is unit - normalize to quoted uppercase
16230            self.write_redshift_date_part_quoted(&func.args[0]);
16231            self.write(", ");
16232            self.generate_expression(&func.args[1])?;
16233            self.write(")");
16234            return Ok(());
16235        }
16236
16237        // TSQL/Fabric: DATE_PART -> DATEPART (no underscore)
16238        if matches!(
16239            self.config.dialect,
16240            Some(DialectType::TSQL) | Some(DialectType::Fabric)
16241        ) && (upper_name == "DATE_PART" || upper_name == "DATEPART")
16242            && func.args.len() == 2
16243        {
16244            self.write_keyword("DATEPART");
16245            self.write("(");
16246            self.generate_expression(&func.args[0])?;
16247            self.write(", ");
16248            self.generate_expression(&func.args[1])?;
16249            self.write(")");
16250            return Ok(());
16251        }
16252
16253        // PostgreSQL/Redshift: DATE_PART(part, value) -> EXTRACT(part FROM value)
16254        if matches!(
16255            self.config.dialect,
16256            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
16257        ) && (upper_name == "DATE_PART" || upper_name == "DATEPART")
16258            && func.args.len() == 2
16259        {
16260            self.write_keyword("EXTRACT");
16261            self.write("(");
16262            // Extract the datetime field - if it's a string literal, strip quotes to make it a keyword
16263            match &func.args[0] {
16264                Expression::Literal(crate::expressions::Literal::String(s)) => {
16265                    self.write(&s.to_lowercase());
16266                }
16267                _ => self.generate_expression(&func.args[0])?,
16268            }
16269            self.write_space();
16270            self.write_keyword("FROM");
16271            self.write_space();
16272            self.generate_expression(&func.args[1])?;
16273            self.write(")");
16274            return Ok(());
16275        }
16276
16277        // Dremio: DATE_PART(part, value) -> EXTRACT(part FROM value)
16278        // Also DATE literals in Dremio should be CAST(...AS DATE)
16279        if self.config.dialect == Some(DialectType::Dremio)
16280            && (upper_name == "DATE_PART" || upper_name == "DATEPART")
16281            && func.args.len() == 2
16282        {
16283            self.write_keyword("EXTRACT");
16284            self.write("(");
16285            self.generate_expression(&func.args[0])?;
16286            self.write_space();
16287            self.write_keyword("FROM");
16288            self.write_space();
16289            // For Dremio, DATE literals should become CAST('value' AS DATE)
16290            self.generate_dremio_date_expression(&func.args[1])?;
16291            self.write(")");
16292            return Ok(());
16293        }
16294
16295        // Dremio: CURRENT_DATE_UTC() -> CURRENT_DATE_UTC (no parentheses)
16296        if self.config.dialect == Some(DialectType::Dremio)
16297            && upper_name == "CURRENT_DATE_UTC"
16298            && func.args.is_empty()
16299        {
16300            self.write_keyword("CURRENT_DATE_UTC");
16301            return Ok(());
16302        }
16303
16304        // Dremio: DATETYPE(year, month, day) transformation
16305        // - If all args are integer literals: DATE('YYYY-MM-DD')
16306        // - If args are expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
16307        if self.config.dialect == Some(DialectType::Dremio)
16308            && upper_name == "DATETYPE"
16309            && func.args.len() == 3
16310        {
16311            // Helper function to extract integer from number literal
16312            fn get_int_literal(expr: &Expression) -> Option<i64> {
16313                if let Expression::Literal(crate::expressions::Literal::Number(s)) = expr {
16314                    s.parse::<i64>().ok()
16315                } else {
16316                    None
16317                }
16318            }
16319
16320            // Check if all arguments are integer literals
16321            if let (Some(year), Some(month), Some(day)) = (
16322                get_int_literal(&func.args[0]),
16323                get_int_literal(&func.args[1]),
16324                get_int_literal(&func.args[2]),
16325            ) {
16326                // All are integer literals: DATE('YYYY-MM-DD')
16327                self.write_keyword("DATE");
16328                self.write(&format!("('{:04}-{:02}-{:02}')", year, month, day));
16329                return Ok(());
16330            }
16331
16332            // For expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
16333            self.write_keyword("CAST");
16334            self.write("(");
16335            self.write_keyword("CONCAT");
16336            self.write("(");
16337            self.generate_expression(&func.args[0])?;
16338            self.write(", '-', ");
16339            self.generate_expression(&func.args[1])?;
16340            self.write(", '-', ");
16341            self.generate_expression(&func.args[2])?;
16342            self.write(")");
16343            self.write_space();
16344            self.write_keyword("AS");
16345            self.write_space();
16346            self.write_keyword("DATE");
16347            self.write(")");
16348            return Ok(());
16349        }
16350
16351        // Presto/Trino: DATE_ADD('unit', interval, date) - wrap interval in CAST(...AS BIGINT)
16352        // when it's not an integer literal
16353        let is_presto_like = matches!(
16354            self.config.dialect,
16355            Some(DialectType::Presto) | Some(DialectType::Trino)
16356        );
16357        if is_presto_like && upper_name == "DATE_ADD" && func.args.len() == 3 {
16358            self.write_keyword("DATE_ADD");
16359            self.write("(");
16360            // First arg: unit (pass through as-is, e.g., 'DAY')
16361            self.generate_expression(&func.args[0])?;
16362            self.write(", ");
16363            // Second arg: interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
16364            let interval = &func.args[1];
16365            let needs_cast = !self.returns_integer_type(interval);
16366            if needs_cast {
16367                self.write_keyword("CAST");
16368                self.write("(");
16369            }
16370            self.generate_expression(interval)?;
16371            if needs_cast {
16372                self.write_space();
16373                self.write_keyword("AS");
16374                self.write_space();
16375                self.write_keyword("BIGINT");
16376                self.write(")");
16377            }
16378            self.write(", ");
16379            // Third arg: date
16380            self.generate_expression(&func.args[2])?;
16381            self.write(")");
16382            return Ok(());
16383        }
16384
16385        // Use bracket syntax if the function was parsed with brackets (e.g., MAP[keys, values])
16386        let use_brackets = func.use_bracket_syntax;
16387
16388        // Special case: functions WITH ORDINALITY need special output order
16389        // Input: FUNC(args) WITH ORDINALITY
16390        // Stored as: name="FUNC WITH ORDINALITY", args=[...]
16391        // Output must be: FUNC(args) WITH ORDINALITY
16392        let has_ordinality = upper_name.ends_with(" WITH ORDINALITY");
16393        let output_name = if has_ordinality {
16394            let base_name = &func.name[..func.name.len() - " WITH ORDINALITY".len()];
16395            self.normalize_func_name(base_name)
16396        } else {
16397            normalized_name.clone()
16398        };
16399
16400        // For qualified names (schema.function or object.method), preserve original case
16401        // because they can be case-sensitive (e.g., TSQL XML methods like .nodes(), .value())
16402        if func.name.contains('.') && !has_ordinality {
16403            // Don't normalize qualified functions - preserve original case
16404            // If the function was quoted (e.g., BigQuery `p.d.UdF`), wrap it in backticks
16405            if func.quoted {
16406                self.write("`");
16407                self.write(&func.name);
16408                self.write("`");
16409            } else {
16410                self.write(&func.name);
16411            }
16412        } else {
16413            self.write(&output_name);
16414        }
16415
16416        // If no_parens is true and there are no args, output just the function name
16417        // Unless the target dialect requires parens for this function
16418        let force_parens = func.no_parens && func.args.is_empty() && !func.distinct && {
16419            let needs_parens = match upper_name.as_str() {
16420                "CURRENT_USER" | "SESSION_USER" | "SYSTEM_USER" => matches!(
16421                    self.config.dialect,
16422                    Some(DialectType::Snowflake)
16423                        | Some(DialectType::Spark)
16424                        | Some(DialectType::Databricks)
16425                        | Some(DialectType::Hive)
16426                ),
16427                _ => false,
16428            };
16429            !needs_parens
16430        };
16431        if force_parens {
16432            // Output trailing comments
16433            for comment in &func.trailing_comments {
16434                self.write_space();
16435                self.write_formatted_comment(comment);
16436            }
16437            return Ok(());
16438        }
16439
16440        // CUBE, ROLLUP, GROUPING SETS need a space before the parenthesis
16441        if upper_name == "CUBE" || upper_name == "ROLLUP" || upper_name == "GROUPING SETS" {
16442            self.write(" (");
16443        } else if use_brackets {
16444            self.write("[");
16445        } else {
16446            self.write("(");
16447        }
16448        if func.distinct {
16449            self.write_keyword("DISTINCT");
16450            self.write_space();
16451        }
16452
16453        // Check if arguments should be split onto multiple lines (pretty + too wide)
16454        let compact_pretty_func = matches!(self.config.dialect, Some(DialectType::Snowflake))
16455            && (upper_name == "TABLE" || upper_name == "FLATTEN");
16456        // GROUPING SETS, CUBE, ROLLUP always expand in pretty mode
16457        let is_grouping_func =
16458            upper_name == "GROUPING SETS" || upper_name == "CUBE" || upper_name == "ROLLUP";
16459        let should_split = if self.config.pretty && !func.args.is_empty() && !compact_pretty_func {
16460            if is_grouping_func {
16461                true
16462            } else {
16463                // Pre-render arguments to check total width
16464                let mut expr_strings: Vec<String> = Vec::with_capacity(func.args.len());
16465                for arg in &func.args {
16466                    let mut temp_gen = Generator::with_config(self.config.clone());
16467                    temp_gen.config.pretty = false; // Don't recurse into pretty
16468                    temp_gen.generate_expression(arg)?;
16469                    expr_strings.push(temp_gen.output);
16470                }
16471                self.too_wide(&expr_strings)
16472            }
16473        } else {
16474            false
16475        };
16476
16477        if should_split {
16478            // Split onto multiple lines
16479            self.write_newline();
16480            self.indent_level += 1;
16481            for (i, arg) in func.args.iter().enumerate() {
16482                self.write_indent();
16483                self.generate_expression(arg)?;
16484                if i + 1 < func.args.len() {
16485                    self.write(",");
16486                }
16487                self.write_newline();
16488            }
16489            self.indent_level -= 1;
16490            self.write_indent();
16491        } else {
16492            // All on one line
16493            for (i, arg) in func.args.iter().enumerate() {
16494                if i > 0 {
16495                    self.write(", ");
16496                }
16497                self.generate_expression(arg)?;
16498            }
16499        }
16500
16501        if use_brackets {
16502            self.write("]");
16503        } else {
16504            self.write(")");
16505        }
16506        // Append WITH ORDINALITY after closing paren for table-valued functions
16507        if has_ordinality {
16508            self.write_space();
16509            self.write_keyword("WITH ORDINALITY");
16510        }
16511        // Output trailing comments
16512        for comment in &func.trailing_comments {
16513            self.write_space();
16514            self.write_formatted_comment(comment);
16515        }
16516        Ok(())
16517    }
16518
16519    fn generate_aggregate_function(&mut self, func: &AggregateFunction) -> Result<()> {
16520        // Normalize function name based on dialect settings
16521        let mut normalized_name = self.normalize_func_name(&func.name);
16522
16523        // Dialect-specific name mappings for aggregate functions
16524        let upper = normalized_name.to_uppercase();
16525        if upper == "MAX_BY" || upper == "MIN_BY" {
16526            let is_max = upper == "MAX_BY";
16527            match self.config.dialect {
16528                Some(DialectType::ClickHouse) => {
16529                    normalized_name = if is_max {
16530                        "argMax".to_string()
16531                    } else {
16532                        "argMin".to_string()
16533                    };
16534                }
16535                Some(DialectType::DuckDB) => {
16536                    normalized_name = if is_max {
16537                        "ARG_MAX".to_string()
16538                    } else {
16539                        "ARG_MIN".to_string()
16540                    };
16541                }
16542                _ => {}
16543            }
16544        }
16545        self.write(&normalized_name);
16546        self.write("(");
16547        if func.distinct {
16548            self.write_keyword("DISTINCT");
16549            self.write_space();
16550        }
16551
16552        // Check if we need to transform multi-arg COUNT DISTINCT
16553        // When dialect doesn't support multi_arg_distinct, transform:
16554        // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
16555        let is_count = normalized_name.eq_ignore_ascii_case("COUNT");
16556        let needs_multi_arg_transform =
16557            func.distinct && is_count && func.args.len() > 1 && !self.config.multi_arg_distinct;
16558
16559        if needs_multi_arg_transform {
16560            // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
16561            self.write_keyword("CASE");
16562            for arg in &func.args {
16563                self.write_space();
16564                self.write_keyword("WHEN");
16565                self.write_space();
16566                self.generate_expression(arg)?;
16567                self.write_space();
16568                self.write_keyword("IS NULL THEN NULL");
16569            }
16570            self.write_space();
16571            self.write_keyword("ELSE");
16572            self.write(" (");
16573            for (i, arg) in func.args.iter().enumerate() {
16574                if i > 0 {
16575                    self.write(", ");
16576                }
16577                self.generate_expression(arg)?;
16578            }
16579            self.write(")");
16580            self.write_space();
16581            self.write_keyword("END");
16582        } else {
16583            for (i, arg) in func.args.iter().enumerate() {
16584                if i > 0 {
16585                    self.write(", ");
16586                }
16587                self.generate_expression(arg)?;
16588            }
16589        }
16590
16591        // IGNORE NULLS / RESPECT NULLS inside parens (for BigQuery style or when config says in_func)
16592        if self.config.ignore_nulls_in_func
16593            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
16594        {
16595            if let Some(ignore) = func.ignore_nulls {
16596                self.write_space();
16597                if ignore {
16598                    self.write_keyword("IGNORE NULLS");
16599                } else {
16600                    self.write_keyword("RESPECT NULLS");
16601                }
16602            }
16603        }
16604
16605        // ORDER BY inside aggregate
16606        if !func.order_by.is_empty() {
16607            self.write_space();
16608            self.write_keyword("ORDER BY");
16609            self.write_space();
16610            for (i, ord) in func.order_by.iter().enumerate() {
16611                if i > 0 {
16612                    self.write(", ");
16613                }
16614                self.generate_ordered(ord)?;
16615            }
16616        }
16617
16618        // LIMIT inside aggregate
16619        if let Some(limit) = &func.limit {
16620            self.write_space();
16621            self.write_keyword("LIMIT");
16622            self.write_space();
16623            // Check if this is a Tuple representing LIMIT offset, count
16624            if let Expression::Tuple(t) = limit.as_ref() {
16625                if t.expressions.len() == 2 {
16626                    self.generate_expression(&t.expressions[0])?;
16627                    self.write(", ");
16628                    self.generate_expression(&t.expressions[1])?;
16629                } else {
16630                    self.generate_expression(limit)?;
16631                }
16632            } else {
16633                self.generate_expression(limit)?;
16634            }
16635        }
16636
16637        self.write(")");
16638
16639        // IGNORE NULLS / RESPECT NULLS outside parens (standard style)
16640        if !self.config.ignore_nulls_in_func
16641            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
16642        {
16643            if let Some(ignore) = func.ignore_nulls {
16644                self.write_space();
16645                if ignore {
16646                    self.write_keyword("IGNORE NULLS");
16647                } else {
16648                    self.write_keyword("RESPECT NULLS");
16649                }
16650            }
16651        }
16652
16653        if let Some(filter) = &func.filter {
16654            self.write_space();
16655            self.write_keyword("FILTER");
16656            self.write("(");
16657            self.write_keyword("WHERE");
16658            self.write_space();
16659            self.generate_expression(filter)?;
16660            self.write(")");
16661        }
16662
16663        Ok(())
16664    }
16665
16666    fn generate_window_function(&mut self, wf: &WindowFunction) -> Result<()> {
16667        self.generate_expression(&wf.this)?;
16668
16669        // Generate KEEP clause if present (Oracle KEEP (DENSE_RANK FIRST|LAST ORDER BY ...))
16670        if let Some(keep) = &wf.keep {
16671            self.write_space();
16672            self.write_keyword("KEEP");
16673            self.write(" (");
16674            self.write_keyword("DENSE_RANK");
16675            self.write_space();
16676            if keep.first {
16677                self.write_keyword("FIRST");
16678            } else {
16679                self.write_keyword("LAST");
16680            }
16681            self.write_space();
16682            self.write_keyword("ORDER BY");
16683            self.write_space();
16684            for (i, ord) in keep.order_by.iter().enumerate() {
16685                if i > 0 {
16686                    self.write(", ");
16687                }
16688                self.generate_ordered(ord)?;
16689            }
16690            self.write(")");
16691        }
16692
16693        // Check if there's any OVER clause content
16694        let has_over = !wf.over.partition_by.is_empty()
16695            || !wf.over.order_by.is_empty()
16696            || wf.over.frame.is_some()
16697            || wf.over.window_name.is_some();
16698
16699        // Only output OVER if there's actual window specification (not just KEEP alone)
16700        if has_over {
16701            self.write_space();
16702            self.write_keyword("OVER");
16703
16704            // Check if this is just a bare named window reference (no parens needed)
16705            let has_specs = !wf.over.partition_by.is_empty()
16706                || !wf.over.order_by.is_empty()
16707                || wf.over.frame.is_some();
16708
16709            if wf.over.window_name.is_some() && !has_specs {
16710                // OVER window_name (without parentheses)
16711                self.write_space();
16712                self.write(&wf.over.window_name.as_ref().unwrap().name);
16713            } else {
16714                // OVER (...) or OVER (window_name ...)
16715                self.write(" (");
16716                self.generate_over(&wf.over)?;
16717                self.write(")");
16718            }
16719        } else if wf.keep.is_none() {
16720            // No KEEP and no OVER content, but still a WindowFunction - output empty OVER ()
16721            self.write_space();
16722            self.write_keyword("OVER");
16723            self.write(" ()");
16724        }
16725
16726        Ok(())
16727    }
16728
16729    /// Generate WITHIN GROUP clause (for ordered-set aggregate functions)
16730    fn generate_within_group(&mut self, wg: &WithinGroup) -> Result<()> {
16731        self.generate_expression(&wg.this)?;
16732        self.write_space();
16733        self.write_keyword("WITHIN GROUP");
16734        self.write(" (");
16735        self.write_keyword("ORDER BY");
16736        self.write_space();
16737        for (i, ord) in wg.order_by.iter().enumerate() {
16738            if i > 0 {
16739                self.write(", ");
16740            }
16741            self.generate_ordered(ord)?;
16742        }
16743        self.write(")");
16744        Ok(())
16745    }
16746
16747    /// Generate the contents of an OVER clause (without parentheses)
16748    fn generate_over(&mut self, over: &Over) -> Result<()> {
16749        let mut has_content = false;
16750
16751        // Named window reference
16752        if let Some(name) = &over.window_name {
16753            self.write(&name.name);
16754            has_content = true;
16755        }
16756
16757        // PARTITION BY
16758        if !over.partition_by.is_empty() {
16759            if has_content {
16760                self.write_space();
16761            }
16762            self.write_keyword("PARTITION BY");
16763            self.write_space();
16764            for (i, expr) in over.partition_by.iter().enumerate() {
16765                if i > 0 {
16766                    self.write(", ");
16767                }
16768                self.generate_expression(expr)?;
16769            }
16770            has_content = true;
16771        }
16772
16773        // ORDER BY
16774        if !over.order_by.is_empty() {
16775            if has_content {
16776                self.write_space();
16777            }
16778            self.write_keyword("ORDER BY");
16779            self.write_space();
16780            for (i, ordered) in over.order_by.iter().enumerate() {
16781                if i > 0 {
16782                    self.write(", ");
16783                }
16784                self.generate_ordered(ordered)?;
16785            }
16786            has_content = true;
16787        }
16788
16789        // Window frame
16790        if let Some(frame) = &over.frame {
16791            if has_content {
16792                self.write_space();
16793            }
16794            self.generate_window_frame(frame)?;
16795        }
16796
16797        Ok(())
16798    }
16799
16800    fn generate_window_frame(&mut self, frame: &WindowFrame) -> Result<()> {
16801        // Exasol uses lowercase for frame kind (rows/range/groups)
16802        let lowercase_frame = self.config.lowercase_window_frame_keywords;
16803
16804        // Use preserved kind_text if available (for case preservation), unless lowercase override is active
16805        if !lowercase_frame {
16806            if let Some(kind_text) = &frame.kind_text {
16807                self.write(kind_text);
16808            } else {
16809                match frame.kind {
16810                    WindowFrameKind::Rows => self.write_keyword("ROWS"),
16811                    WindowFrameKind::Range => self.write_keyword("RANGE"),
16812                    WindowFrameKind::Groups => self.write_keyword("GROUPS"),
16813                }
16814            }
16815        } else {
16816            match frame.kind {
16817                WindowFrameKind::Rows => self.write("rows"),
16818                WindowFrameKind::Range => self.write("range"),
16819                WindowFrameKind::Groups => self.write("groups"),
16820            }
16821        }
16822
16823        // Use BETWEEN format only when there's an explicit end bound,
16824        // or when normalize_window_frame_between is enabled and the start is a directional bound
16825        self.write_space();
16826        let should_normalize = self.config.normalize_window_frame_between
16827            && frame.end.is_none()
16828            && matches!(
16829                frame.start,
16830                WindowFrameBound::Preceding(_)
16831                    | WindowFrameBound::Following(_)
16832                    | WindowFrameBound::UnboundedPreceding
16833                    | WindowFrameBound::UnboundedFollowing
16834            );
16835
16836        if let Some(end) = &frame.end {
16837            // BETWEEN format: RANGE BETWEEN start AND end
16838            self.write_keyword("BETWEEN");
16839            self.write_space();
16840            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
16841            self.write_space();
16842            self.write_keyword("AND");
16843            self.write_space();
16844            self.generate_window_frame_bound(end, frame.end_side_text.as_deref())?;
16845        } else if should_normalize {
16846            // Normalize single-bound to BETWEEN form: ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
16847            self.write_keyword("BETWEEN");
16848            self.write_space();
16849            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
16850            self.write_space();
16851            self.write_keyword("AND");
16852            self.write_space();
16853            self.write_keyword("CURRENT ROW");
16854        } else {
16855            // Single bound format: RANGE CURRENT ROW
16856            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
16857        }
16858
16859        // EXCLUDE clause
16860        if let Some(exclude) = &frame.exclude {
16861            self.write_space();
16862            self.write_keyword("EXCLUDE");
16863            self.write_space();
16864            match exclude {
16865                WindowFrameExclude::CurrentRow => self.write_keyword("CURRENT ROW"),
16866                WindowFrameExclude::Group => self.write_keyword("GROUP"),
16867                WindowFrameExclude::Ties => self.write_keyword("TIES"),
16868                WindowFrameExclude::NoOthers => self.write_keyword("NO OTHERS"),
16869            }
16870        }
16871
16872        Ok(())
16873    }
16874
16875    fn generate_window_frame_bound(
16876        &mut self,
16877        bound: &WindowFrameBound,
16878        side_text: Option<&str>,
16879    ) -> Result<()> {
16880        // Exasol uses lowercase for preceding/following
16881        let lowercase_frame = self.config.lowercase_window_frame_keywords;
16882
16883        match bound {
16884            WindowFrameBound::CurrentRow => {
16885                self.write_keyword("CURRENT ROW");
16886            }
16887            WindowFrameBound::UnboundedPreceding => {
16888                self.write_keyword("UNBOUNDED");
16889                self.write_space();
16890                if lowercase_frame {
16891                    self.write("preceding");
16892                } else if let Some(text) = side_text {
16893                    self.write(text);
16894                } else {
16895                    self.write_keyword("PRECEDING");
16896                }
16897            }
16898            WindowFrameBound::UnboundedFollowing => {
16899                self.write_keyword("UNBOUNDED");
16900                self.write_space();
16901                if lowercase_frame {
16902                    self.write("following");
16903                } else if let Some(text) = side_text {
16904                    self.write(text);
16905                } else {
16906                    self.write_keyword("FOLLOWING");
16907                }
16908            }
16909            WindowFrameBound::Preceding(expr) => {
16910                self.generate_expression(expr)?;
16911                self.write_space();
16912                if lowercase_frame {
16913                    self.write("preceding");
16914                } else if let Some(text) = side_text {
16915                    self.write(text);
16916                } else {
16917                    self.write_keyword("PRECEDING");
16918                }
16919            }
16920            WindowFrameBound::Following(expr) => {
16921                self.generate_expression(expr)?;
16922                self.write_space();
16923                if lowercase_frame {
16924                    self.write("following");
16925                } else if let Some(text) = side_text {
16926                    self.write(text);
16927                } else {
16928                    self.write_keyword("FOLLOWING");
16929                }
16930            }
16931            WindowFrameBound::BarePreceding => {
16932                if lowercase_frame {
16933                    self.write("preceding");
16934                } else if let Some(text) = side_text {
16935                    self.write(text);
16936                } else {
16937                    self.write_keyword("PRECEDING");
16938                }
16939            }
16940            WindowFrameBound::BareFollowing => {
16941                if lowercase_frame {
16942                    self.write("following");
16943                } else if let Some(text) = side_text {
16944                    self.write(text);
16945                } else {
16946                    self.write_keyword("FOLLOWING");
16947                }
16948            }
16949            WindowFrameBound::Value(expr) => {
16950                // Bare numeric bound without PRECEDING/FOLLOWING
16951                self.generate_expression(expr)?;
16952            }
16953        }
16954        Ok(())
16955    }
16956
16957    fn generate_interval(&mut self, interval: &Interval) -> Result<()> {
16958        // For Oracle with ExprSpan: only output INTERVAL if `this` is a literal
16959        // (e.g., `(expr) DAY(9) TO SECOND(3)` should NOT have INTERVAL prefix)
16960        let skip_interval_keyword = matches!(self.config.dialect, Some(DialectType::Oracle))
16961            && matches!(&interval.unit, Some(IntervalUnitSpec::ExprSpan(_)))
16962            && !matches!(&interval.this, Some(Expression::Literal(_)));
16963
16964        // SINGLE_STRING_INTERVAL: combine value and unit into a single quoted string
16965        // e.g., INTERVAL '1' DAY -> INTERVAL '1 DAY'
16966        if self.config.single_string_interval {
16967            if let (
16968                Some(Expression::Literal(Literal::String(ref val))),
16969                Some(IntervalUnitSpec::Simple {
16970                    ref unit,
16971                    ref use_plural,
16972                }),
16973            ) = (&interval.this, &interval.unit)
16974            {
16975                self.write_keyword("INTERVAL");
16976                self.write_space();
16977                let effective_plural = *use_plural && self.config.interval_allows_plural_form;
16978                let unit_str = self.interval_unit_str(unit, effective_plural);
16979                self.write("'");
16980                self.write(val);
16981                self.write(" ");
16982                self.write(&unit_str);
16983                self.write("'");
16984                return Ok(());
16985            }
16986        }
16987
16988        if !skip_interval_keyword {
16989            self.write_keyword("INTERVAL");
16990        }
16991
16992        // Generate value if present
16993        if let Some(ref value) = interval.this {
16994            if !skip_interval_keyword {
16995                self.write_space();
16996            }
16997            // If the value is a complex expression (not a literal/column/function call)
16998            // and there's a unit, wrap it in parentheses
16999            // e.g., INTERVAL (2 * 2) MONTH, INTERVAL (DAYOFMONTH(dt) - 1) DAY
17000            let needs_parens = interval.unit.is_some()
17001                && matches!(
17002                    value,
17003                    Expression::Add(_)
17004                        | Expression::Sub(_)
17005                        | Expression::Mul(_)
17006                        | Expression::Div(_)
17007                        | Expression::Mod(_)
17008                        | Expression::BitwiseAnd(_)
17009                        | Expression::BitwiseOr(_)
17010                        | Expression::BitwiseXor(_)
17011                );
17012            if needs_parens {
17013                self.write("(");
17014            }
17015            self.generate_expression(value)?;
17016            if needs_parens {
17017                self.write(")");
17018            }
17019        }
17020
17021        // Generate unit if present
17022        if let Some(ref unit_spec) = interval.unit {
17023            self.write_space();
17024            self.write_interval_unit_spec(unit_spec)?;
17025        }
17026
17027        Ok(())
17028    }
17029
17030    /// Return the string representation of an interval unit
17031    fn interval_unit_str(&self, unit: &IntervalUnit, use_plural: bool) -> &'static str {
17032        match (unit, use_plural) {
17033            (IntervalUnit::Year, false) => "YEAR",
17034            (IntervalUnit::Year, true) => "YEARS",
17035            (IntervalUnit::Quarter, false) => "QUARTER",
17036            (IntervalUnit::Quarter, true) => "QUARTERS",
17037            (IntervalUnit::Month, false) => "MONTH",
17038            (IntervalUnit::Month, true) => "MONTHS",
17039            (IntervalUnit::Week, false) => "WEEK",
17040            (IntervalUnit::Week, true) => "WEEKS",
17041            (IntervalUnit::Day, false) => "DAY",
17042            (IntervalUnit::Day, true) => "DAYS",
17043            (IntervalUnit::Hour, false) => "HOUR",
17044            (IntervalUnit::Hour, true) => "HOURS",
17045            (IntervalUnit::Minute, false) => "MINUTE",
17046            (IntervalUnit::Minute, true) => "MINUTES",
17047            (IntervalUnit::Second, false) => "SECOND",
17048            (IntervalUnit::Second, true) => "SECONDS",
17049            (IntervalUnit::Millisecond, false) => "MILLISECOND",
17050            (IntervalUnit::Millisecond, true) => "MILLISECONDS",
17051            (IntervalUnit::Microsecond, false) => "MICROSECOND",
17052            (IntervalUnit::Microsecond, true) => "MICROSECONDS",
17053            (IntervalUnit::Nanosecond, false) => "NANOSECOND",
17054            (IntervalUnit::Nanosecond, true) => "NANOSECONDS",
17055        }
17056    }
17057
17058    fn write_interval_unit_spec(&mut self, unit_spec: &IntervalUnitSpec) -> Result<()> {
17059        match unit_spec {
17060            IntervalUnitSpec::Simple { unit, use_plural } => {
17061                // If dialect doesn't allow plural forms, force singular
17062                let effective_plural = *use_plural && self.config.interval_allows_plural_form;
17063                self.write_simple_interval_unit(unit, effective_plural);
17064            }
17065            IntervalUnitSpec::Span(span) => {
17066                self.write_simple_interval_unit(&span.this, false);
17067                self.write_space();
17068                self.write_keyword("TO");
17069                self.write_space();
17070                self.write_simple_interval_unit(&span.expression, false);
17071            }
17072            IntervalUnitSpec::ExprSpan(span) => {
17073                // Expression-based interval span (e.g., DAY(9) TO SECOND(3))
17074                self.generate_expression(&span.this)?;
17075                self.write_space();
17076                self.write_keyword("TO");
17077                self.write_space();
17078                self.generate_expression(&span.expression)?;
17079            }
17080            IntervalUnitSpec::Expr(expr) => {
17081                self.generate_expression(expr)?;
17082            }
17083        }
17084        Ok(())
17085    }
17086
17087    fn write_simple_interval_unit(&mut self, unit: &IntervalUnit, use_plural: bool) {
17088        // Output interval unit, respecting plural preference
17089        match (unit, use_plural) {
17090            (IntervalUnit::Year, false) => self.write_keyword("YEAR"),
17091            (IntervalUnit::Year, true) => self.write_keyword("YEARS"),
17092            (IntervalUnit::Quarter, false) => self.write_keyword("QUARTER"),
17093            (IntervalUnit::Quarter, true) => self.write_keyword("QUARTERS"),
17094            (IntervalUnit::Month, false) => self.write_keyword("MONTH"),
17095            (IntervalUnit::Month, true) => self.write_keyword("MONTHS"),
17096            (IntervalUnit::Week, false) => self.write_keyword("WEEK"),
17097            (IntervalUnit::Week, true) => self.write_keyword("WEEKS"),
17098            (IntervalUnit::Day, false) => self.write_keyword("DAY"),
17099            (IntervalUnit::Day, true) => self.write_keyword("DAYS"),
17100            (IntervalUnit::Hour, false) => self.write_keyword("HOUR"),
17101            (IntervalUnit::Hour, true) => self.write_keyword("HOURS"),
17102            (IntervalUnit::Minute, false) => self.write_keyword("MINUTE"),
17103            (IntervalUnit::Minute, true) => self.write_keyword("MINUTES"),
17104            (IntervalUnit::Second, false) => self.write_keyword("SECOND"),
17105            (IntervalUnit::Second, true) => self.write_keyword("SECONDS"),
17106            (IntervalUnit::Millisecond, false) => self.write_keyword("MILLISECOND"),
17107            (IntervalUnit::Millisecond, true) => self.write_keyword("MILLISECONDS"),
17108            (IntervalUnit::Microsecond, false) => self.write_keyword("MICROSECOND"),
17109            (IntervalUnit::Microsecond, true) => self.write_keyword("MICROSECONDS"),
17110            (IntervalUnit::Nanosecond, false) => self.write_keyword("NANOSECOND"),
17111            (IntervalUnit::Nanosecond, true) => self.write_keyword("NANOSECONDS"),
17112        }
17113    }
17114
17115    /// Normalize a date part expression to unquoted uppercase for Redshift DATEDIFF/DATEADD
17116    /// Converts: 'day', 'days', day, days, DAY -> DAY (unquoted)
17117    fn write_redshift_date_part(&mut self, expr: &Expression) {
17118        let part_str = self.extract_date_part_string(expr);
17119        if let Some(part) = part_str {
17120            let normalized = self.normalize_date_part(&part);
17121            self.write_keyword(&normalized);
17122        } else {
17123            // If we can't extract a date part string, fall back to generating the expression
17124            let _ = self.generate_expression(expr);
17125        }
17126    }
17127
17128    /// Normalize a date part expression to quoted uppercase for Redshift DATE_TRUNC
17129    /// Converts: 'day', day, DAY -> 'DAY' (quoted)
17130    fn write_redshift_date_part_quoted(&mut self, expr: &Expression) {
17131        let part_str = self.extract_date_part_string(expr);
17132        if let Some(part) = part_str {
17133            let normalized = self.normalize_date_part(&part);
17134            self.write("'");
17135            self.write(&normalized);
17136            self.write("'");
17137        } else {
17138            // If we can't extract a date part string, fall back to generating the expression
17139            let _ = self.generate_expression(expr);
17140        }
17141    }
17142
17143    /// Extract date part string from expression (handles string literals and identifiers)
17144    fn extract_date_part_string(&self, expr: &Expression) -> Option<String> {
17145        match expr {
17146            Expression::Literal(crate::expressions::Literal::String(s)) => Some(s.clone()),
17147            Expression::Identifier(id) => Some(id.name.clone()),
17148            Expression::Column(col) if col.table.is_none() => {
17149                // Simple column reference without table prefix, treat as identifier
17150                Some(col.name.name.clone())
17151            }
17152            _ => None,
17153        }
17154    }
17155
17156    /// Normalize date part to uppercase singular form
17157    /// days -> DAY, months -> MONTH, etc.
17158    fn normalize_date_part(&self, part: &str) -> String {
17159        let lower = part.to_lowercase();
17160        match lower.as_str() {
17161            "day" | "days" | "d" => "DAY".to_string(),
17162            "month" | "months" | "mon" | "mm" => "MONTH".to_string(),
17163            "year" | "years" | "y" | "yy" | "yyyy" => "YEAR".to_string(),
17164            "week" | "weeks" | "w" | "wk" => "WEEK".to_string(),
17165            "hour" | "hours" | "h" | "hh" => "HOUR".to_string(),
17166            "minute" | "minutes" | "m" | "mi" | "n" => "MINUTE".to_string(),
17167            "second" | "seconds" | "s" | "ss" => "SECOND".to_string(),
17168            "millisecond" | "milliseconds" | "ms" => "MILLISECOND".to_string(),
17169            "microsecond" | "microseconds" | "us" => "MICROSECOND".to_string(),
17170            "quarter" | "quarters" | "q" | "qq" => "QUARTER".to_string(),
17171            _ => part.to_uppercase(),
17172        }
17173    }
17174
17175    fn write_datetime_field(&mut self, field: &DateTimeField) {
17176        match field {
17177            DateTimeField::Year => self.write_keyword("YEAR"),
17178            DateTimeField::Month => self.write_keyword("MONTH"),
17179            DateTimeField::Day => self.write_keyword("DAY"),
17180            DateTimeField::Hour => self.write_keyword("HOUR"),
17181            DateTimeField::Minute => self.write_keyword("MINUTE"),
17182            DateTimeField::Second => self.write_keyword("SECOND"),
17183            DateTimeField::Millisecond => self.write_keyword("MILLISECOND"),
17184            DateTimeField::Microsecond => self.write_keyword("MICROSECOND"),
17185            DateTimeField::DayOfWeek => {
17186                let name = match self.config.dialect {
17187                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFWEEK",
17188                    _ => "DOW",
17189                };
17190                self.write_keyword(name);
17191            }
17192            DateTimeField::DayOfYear => {
17193                let name = match self.config.dialect {
17194                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFYEAR",
17195                    _ => "DOY",
17196                };
17197                self.write_keyword(name);
17198            }
17199            DateTimeField::Week => self.write_keyword("WEEK"),
17200            DateTimeField::WeekWithModifier(modifier) => {
17201                self.write_keyword("WEEK");
17202                self.write("(");
17203                self.write(modifier);
17204                self.write(")");
17205            }
17206            DateTimeField::Quarter => self.write_keyword("QUARTER"),
17207            DateTimeField::Epoch => self.write_keyword("EPOCH"),
17208            DateTimeField::Timezone => self.write_keyword("TIMEZONE"),
17209            DateTimeField::TimezoneHour => self.write_keyword("TIMEZONE_HOUR"),
17210            DateTimeField::TimezoneMinute => self.write_keyword("TIMEZONE_MINUTE"),
17211            DateTimeField::Date => self.write_keyword("DATE"),
17212            DateTimeField::Time => self.write_keyword("TIME"),
17213            DateTimeField::Custom(name) => self.write(name),
17214        }
17215    }
17216
17217    /// Write datetime field in lowercase (for Spark/Hive/Databricks)
17218    fn write_datetime_field_lower(&mut self, field: &DateTimeField) {
17219        match field {
17220            DateTimeField::Year => self.write("year"),
17221            DateTimeField::Month => self.write("month"),
17222            DateTimeField::Day => self.write("day"),
17223            DateTimeField::Hour => self.write("hour"),
17224            DateTimeField::Minute => self.write("minute"),
17225            DateTimeField::Second => self.write("second"),
17226            DateTimeField::Millisecond => self.write("millisecond"),
17227            DateTimeField::Microsecond => self.write("microsecond"),
17228            DateTimeField::DayOfWeek => self.write("dow"),
17229            DateTimeField::DayOfYear => self.write("doy"),
17230            DateTimeField::Week => self.write("week"),
17231            DateTimeField::WeekWithModifier(modifier) => {
17232                self.write("week(");
17233                self.write(modifier);
17234                self.write(")");
17235            }
17236            DateTimeField::Quarter => self.write("quarter"),
17237            DateTimeField::Epoch => self.write("epoch"),
17238            DateTimeField::Timezone => self.write("timezone"),
17239            DateTimeField::TimezoneHour => self.write("timezone_hour"),
17240            DateTimeField::TimezoneMinute => self.write("timezone_minute"),
17241            DateTimeField::Date => self.write("date"),
17242            DateTimeField::Time => self.write("time"),
17243            DateTimeField::Custom(name) => self.write(name),
17244        }
17245    }
17246
17247    // Helper function generators
17248
17249    fn generate_simple_func(&mut self, name: &str, arg: &Expression) -> Result<()> {
17250        self.write_keyword(name);
17251        self.write("(");
17252        self.generate_expression(arg)?;
17253        self.write(")");
17254        Ok(())
17255    }
17256
17257    /// Generate a unary function, using the original name if available for round-trip preservation
17258    fn generate_unary_func(
17259        &mut self,
17260        default_name: &str,
17261        f: &crate::expressions::UnaryFunc,
17262    ) -> Result<()> {
17263        let name = f.original_name.as_deref().unwrap_or(default_name);
17264        self.write_keyword(name);
17265        self.write("(");
17266        self.generate_expression(&f.this)?;
17267        self.write(")");
17268        Ok(())
17269    }
17270
17271    /// Generate SQRT/CBRT - always use function form (matches Python SQLGlot normalization)
17272    fn generate_sqrt_cbrt(
17273        &mut self,
17274        f: &crate::expressions::UnaryFunc,
17275        func_name: &str,
17276        _op: &str,
17277    ) -> Result<()> {
17278        // Python SQLGlot normalizes |/ and ||/ to SQRT() and CBRT()
17279        // Always use function syntax for consistency
17280        self.write_keyword(func_name);
17281        self.write("(");
17282        self.generate_expression(&f.this)?;
17283        self.write(")");
17284        Ok(())
17285    }
17286
17287    fn generate_binary_func(
17288        &mut self,
17289        name: &str,
17290        arg1: &Expression,
17291        arg2: &Expression,
17292    ) -> Result<()> {
17293        self.write_keyword(name);
17294        self.write("(");
17295        self.generate_expression(arg1)?;
17296        self.write(", ");
17297        self.generate_expression(arg2)?;
17298        self.write(")");
17299        Ok(())
17300    }
17301
17302    /// Generate CHAR/CHR function with optional USING charset
17303    /// e.g., CHAR(77, 77.3, '77.3' USING utf8mb4)
17304    /// e.g., CHR(187 USING NCHAR_CS) -- Oracle
17305    fn generate_char_func(&mut self, f: &crate::expressions::CharFunc) -> Result<()> {
17306        // Use stored name if available, otherwise default to CHAR
17307        let func_name = f.name.as_deref().unwrap_or("CHAR");
17308        self.write_keyword(func_name);
17309        self.write("(");
17310        for (i, arg) in f.args.iter().enumerate() {
17311            if i > 0 {
17312                self.write(", ");
17313            }
17314            self.generate_expression(arg)?;
17315        }
17316        if let Some(ref charset) = f.charset {
17317            self.write(" ");
17318            self.write_keyword("USING");
17319            self.write(" ");
17320            self.write(charset);
17321        }
17322        self.write(")");
17323        Ok(())
17324    }
17325
17326    fn generate_power(&mut self, f: &BinaryFunc) -> Result<()> {
17327        use crate::dialects::DialectType;
17328
17329        match self.config.dialect {
17330            Some(DialectType::Teradata) => {
17331                // Teradata uses ** operator for exponentiation
17332                self.generate_expression(&f.this)?;
17333                self.write(" ** ");
17334                self.generate_expression(&f.expression)?;
17335                Ok(())
17336            }
17337            _ => {
17338                // Other dialects use POWER function
17339                self.generate_binary_func("POWER", &f.this, &f.expression)
17340            }
17341        }
17342    }
17343
17344    fn generate_vararg_func(&mut self, name: &str, args: &[Expression]) -> Result<()> {
17345        self.write_func_name(name);
17346        self.write("(");
17347        for (i, arg) in args.iter().enumerate() {
17348            if i > 0 {
17349                self.write(", ");
17350            }
17351            self.generate_expression(arg)?;
17352        }
17353        self.write(")");
17354        Ok(())
17355    }
17356
17357    // String function generators
17358
17359    fn generate_concat_ws(&mut self, f: &ConcatWs) -> Result<()> {
17360        self.write_keyword("CONCAT_WS");
17361        self.write("(");
17362        self.generate_expression(&f.separator)?;
17363        for expr in &f.expressions {
17364            self.write(", ");
17365            self.generate_expression(expr)?;
17366        }
17367        self.write(")");
17368        Ok(())
17369    }
17370
17371    fn generate_substring(&mut self, f: &SubstringFunc) -> Result<()> {
17372        // Oracle uses SUBSTR; most others use SUBSTRING
17373        let is_oracle = matches!(self.config.dialect, Some(DialectType::Oracle));
17374        if is_oracle {
17375            self.write_keyword("SUBSTR");
17376        } else {
17377            self.write_keyword("SUBSTRING");
17378        }
17379        self.write("(");
17380        self.generate_expression(&f.this)?;
17381        // PostgreSQL always uses FROM/FOR syntax
17382        let force_from_for = matches!(self.config.dialect, Some(DialectType::PostgreSQL));
17383        // Spark/Hive use comma syntax, not FROM/FOR syntax
17384        let use_comma_syntax = matches!(
17385            self.config.dialect,
17386            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
17387        );
17388        if (f.from_for_syntax || force_from_for) && !use_comma_syntax {
17389            // SQL standard syntax: SUBSTRING(str FROM pos FOR len)
17390            self.write_space();
17391            self.write_keyword("FROM");
17392            self.write_space();
17393            self.generate_expression(&f.start)?;
17394            if let Some(length) = &f.length {
17395                self.write_space();
17396                self.write_keyword("FOR");
17397                self.write_space();
17398                self.generate_expression(length)?;
17399            }
17400        } else {
17401            // Comma-separated syntax: SUBSTRING(str, pos, len) or SUBSTR(str, pos, len)
17402            self.write(", ");
17403            self.generate_expression(&f.start)?;
17404            if let Some(length) = &f.length {
17405                self.write(", ");
17406                self.generate_expression(length)?;
17407            }
17408        }
17409        self.write(")");
17410        Ok(())
17411    }
17412
17413    fn generate_overlay(&mut self, f: &OverlayFunc) -> Result<()> {
17414        self.write_keyword("OVERLAY");
17415        self.write("(");
17416        self.generate_expression(&f.this)?;
17417        self.write_space();
17418        self.write_keyword("PLACING");
17419        self.write_space();
17420        self.generate_expression(&f.replacement)?;
17421        self.write_space();
17422        self.write_keyword("FROM");
17423        self.write_space();
17424        self.generate_expression(&f.from)?;
17425        if let Some(length) = &f.length {
17426            self.write_space();
17427            self.write_keyword("FOR");
17428            self.write_space();
17429            self.generate_expression(length)?;
17430        }
17431        self.write(")");
17432        Ok(())
17433    }
17434
17435    fn generate_trim(&mut self, f: &TrimFunc) -> Result<()> {
17436        // Special case: TRIM(LEADING str) -> LTRIM(str), TRIM(TRAILING str) -> RTRIM(str)
17437        // when no characters are specified (PostgreSQL style)
17438        if f.position_explicit && f.characters.is_none() {
17439            match f.position {
17440                TrimPosition::Leading => {
17441                    self.write_keyword("LTRIM");
17442                    self.write("(");
17443                    self.generate_expression(&f.this)?;
17444                    self.write(")");
17445                    return Ok(());
17446                }
17447                TrimPosition::Trailing => {
17448                    self.write_keyword("RTRIM");
17449                    self.write("(");
17450                    self.generate_expression(&f.this)?;
17451                    self.write(")");
17452                    return Ok(());
17453                }
17454                TrimPosition::Both => {
17455                    // TRIM(BOTH str) -> BTRIM(str) in PostgreSQL, but TRIM(str) is more standard
17456                    // Fall through to standard TRIM handling
17457                }
17458            }
17459        }
17460
17461        self.write_keyword("TRIM");
17462        self.write("(");
17463        // When BOTH is specified without trim characters, simplify to just TRIM(str)
17464        // Force standard syntax for dialects that require it (Hive, Spark, Databricks, ClickHouse)
17465        let force_standard = f.characters.is_some()
17466            && !f.sql_standard_syntax
17467            && matches!(
17468                self.config.dialect,
17469                Some(DialectType::Hive)
17470                    | Some(DialectType::Spark)
17471                    | Some(DialectType::Databricks)
17472                    | Some(DialectType::ClickHouse)
17473            );
17474        let use_standard = (f.sql_standard_syntax || force_standard)
17475            && !(f.position_explicit
17476                && f.characters.is_none()
17477                && matches!(f.position, TrimPosition::Both));
17478        if use_standard {
17479            // SQL standard syntax: TRIM(BOTH chars FROM str)
17480            // Only output position if it was explicitly specified
17481            if f.position_explicit {
17482                match f.position {
17483                    TrimPosition::Both => self.write_keyword("BOTH"),
17484                    TrimPosition::Leading => self.write_keyword("LEADING"),
17485                    TrimPosition::Trailing => self.write_keyword("TRAILING"),
17486                }
17487                self.write_space();
17488            }
17489            if let Some(chars) = &f.characters {
17490                self.generate_expression(chars)?;
17491                self.write_space();
17492            }
17493            self.write_keyword("FROM");
17494            self.write_space();
17495            self.generate_expression(&f.this)?;
17496        } else {
17497            // Simple function syntax: TRIM(str) or TRIM(str, chars)
17498            self.generate_expression(&f.this)?;
17499            if let Some(chars) = &f.characters {
17500                self.write(", ");
17501                self.generate_expression(chars)?;
17502            }
17503        }
17504        self.write(")");
17505        Ok(())
17506    }
17507
17508    fn generate_replace(&mut self, f: &ReplaceFunc) -> Result<()> {
17509        self.write_keyword("REPLACE");
17510        self.write("(");
17511        self.generate_expression(&f.this)?;
17512        self.write(", ");
17513        self.generate_expression(&f.old)?;
17514        self.write(", ");
17515        self.generate_expression(&f.new)?;
17516        self.write(")");
17517        Ok(())
17518    }
17519
17520    fn generate_left_right(&mut self, name: &str, f: &LeftRightFunc) -> Result<()> {
17521        self.write_keyword(name);
17522        self.write("(");
17523        self.generate_expression(&f.this)?;
17524        self.write(", ");
17525        self.generate_expression(&f.length)?;
17526        self.write(")");
17527        Ok(())
17528    }
17529
17530    fn generate_repeat(&mut self, f: &RepeatFunc) -> Result<()> {
17531        self.write_keyword("REPEAT");
17532        self.write("(");
17533        self.generate_expression(&f.this)?;
17534        self.write(", ");
17535        self.generate_expression(&f.times)?;
17536        self.write(")");
17537        Ok(())
17538    }
17539
17540    fn generate_pad(&mut self, name: &str, f: &PadFunc) -> Result<()> {
17541        self.write_keyword(name);
17542        self.write("(");
17543        self.generate_expression(&f.this)?;
17544        self.write(", ");
17545        self.generate_expression(&f.length)?;
17546        if let Some(fill) = &f.fill {
17547            self.write(", ");
17548            self.generate_expression(fill)?;
17549        }
17550        self.write(")");
17551        Ok(())
17552    }
17553
17554    fn generate_split(&mut self, f: &SplitFunc) -> Result<()> {
17555        self.write_keyword("SPLIT");
17556        self.write("(");
17557        self.generate_expression(&f.this)?;
17558        self.write(", ");
17559        self.generate_expression(&f.delimiter)?;
17560        self.write(")");
17561        Ok(())
17562    }
17563
17564    fn generate_regexp_like(&mut self, f: &RegexpFunc) -> Result<()> {
17565        use crate::dialects::DialectType;
17566        // PostgreSQL uses ~ operator for regex matching
17567        if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) && f.flags.is_none() {
17568            self.generate_expression(&f.this)?;
17569            self.write(" ~ ");
17570            self.generate_expression(&f.pattern)?;
17571        } else if matches!(
17572            self.config.dialect,
17573            Some(DialectType::SingleStore)
17574                | Some(DialectType::Spark)
17575                | Some(DialectType::Hive)
17576                | Some(DialectType::Databricks)
17577        ) && f.flags.is_none()
17578        {
17579            // SingleStore/Spark/Hive/Databricks use RLIKE infix operator
17580            self.generate_expression(&f.this)?;
17581            self.write_keyword(" RLIKE ");
17582            self.generate_expression(&f.pattern)?;
17583        } else if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
17584            // StarRocks uses REGEXP function syntax
17585            self.write_keyword("REGEXP");
17586            self.write("(");
17587            self.generate_expression(&f.this)?;
17588            self.write(", ");
17589            self.generate_expression(&f.pattern)?;
17590            if let Some(flags) = &f.flags {
17591                self.write(", ");
17592                self.generate_expression(flags)?;
17593            }
17594            self.write(")");
17595        } else {
17596            self.write_keyword("REGEXP_LIKE");
17597            self.write("(");
17598            self.generate_expression(&f.this)?;
17599            self.write(", ");
17600            self.generate_expression(&f.pattern)?;
17601            if let Some(flags) = &f.flags {
17602                self.write(", ");
17603                self.generate_expression(flags)?;
17604            }
17605            self.write(")");
17606        }
17607        Ok(())
17608    }
17609
17610    fn generate_regexp_replace(&mut self, f: &RegexpReplaceFunc) -> Result<()> {
17611        self.write_keyword("REGEXP_REPLACE");
17612        self.write("(");
17613        self.generate_expression(&f.this)?;
17614        self.write(", ");
17615        self.generate_expression(&f.pattern)?;
17616        self.write(", ");
17617        self.generate_expression(&f.replacement)?;
17618        if let Some(flags) = &f.flags {
17619            self.write(", ");
17620            self.generate_expression(flags)?;
17621        }
17622        self.write(")");
17623        Ok(())
17624    }
17625
17626    fn generate_regexp_extract(&mut self, f: &RegexpExtractFunc) -> Result<()> {
17627        self.write_keyword("REGEXP_EXTRACT");
17628        self.write("(");
17629        self.generate_expression(&f.this)?;
17630        self.write(", ");
17631        self.generate_expression(&f.pattern)?;
17632        if let Some(group) = &f.group {
17633            self.write(", ");
17634            self.generate_expression(group)?;
17635        }
17636        self.write(")");
17637        Ok(())
17638    }
17639
17640    // Math function generators
17641
17642    fn generate_round(&mut self, f: &RoundFunc) -> Result<()> {
17643        self.write_keyword("ROUND");
17644        self.write("(");
17645        self.generate_expression(&f.this)?;
17646        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_floor(&mut self, f: &FloorFunc) -> Result<()> {
17655        self.write_keyword("FLOOR");
17656        self.write("(");
17657        self.generate_expression(&f.this)?;
17658        // Handle Druid-style FLOOR(time TO unit) syntax
17659        if let Some(to) = &f.to {
17660            self.write(" ");
17661            self.write_keyword("TO");
17662            self.write(" ");
17663            self.generate_expression(to)?;
17664        } else if let Some(scale) = &f.scale {
17665            self.write(", ");
17666            self.generate_expression(scale)?;
17667        }
17668        self.write(")");
17669        Ok(())
17670    }
17671
17672    fn generate_ceil(&mut self, f: &CeilFunc) -> Result<()> {
17673        self.write_keyword("CEIL");
17674        self.write("(");
17675        self.generate_expression(&f.this)?;
17676        // Handle Druid-style CEIL(time TO unit) syntax
17677        if let Some(to) = &f.to {
17678            self.write(" ");
17679            self.write_keyword("TO");
17680            self.write(" ");
17681            self.generate_expression(to)?;
17682        } else if let Some(decimals) = &f.decimals {
17683            self.write(", ");
17684            self.generate_expression(decimals)?;
17685        }
17686        self.write(")");
17687        Ok(())
17688    }
17689
17690    fn generate_log(&mut self, f: &LogFunc) -> Result<()> {
17691        use crate::expressions::Literal;
17692
17693        if let Some(base) = &f.base {
17694            // Check for LOG_BASE_FIRST = None dialects (Presto, Trino, ClickHouse, Athena)
17695            // These dialects use LOG2()/LOG10() instead of LOG(base, value)
17696            if self.is_log_base_none() {
17697                if matches!(base, Expression::Literal(Literal::Number(s)) if s == "2") {
17698                    self.write_func_name("LOG2");
17699                    self.write("(");
17700                    self.generate_expression(&f.this)?;
17701                    self.write(")");
17702                    return Ok(());
17703                } else if matches!(base, Expression::Literal(Literal::Number(s)) if s == "10") {
17704                    self.write_func_name("LOG10");
17705                    self.write("(");
17706                    self.generate_expression(&f.this)?;
17707                    self.write(")");
17708                    return Ok(());
17709                }
17710                // Other bases: fall through to LOG(base, value) — best effort
17711            }
17712
17713            self.write_func_name("LOG");
17714            self.write("(");
17715            if self.is_log_value_first() {
17716                // BigQuery, TSQL, Tableau, Fabric: LOG(value, base)
17717                self.generate_expression(&f.this)?;
17718                self.write(", ");
17719                self.generate_expression(base)?;
17720            } else {
17721                // Default (PostgreSQL, etc.): LOG(base, value)
17722                self.generate_expression(base)?;
17723                self.write(", ");
17724                self.generate_expression(&f.this)?;
17725            }
17726            self.write(")");
17727        } else {
17728            // Single arg: LOG(x) — unspecified base (log base 10 in default dialect)
17729            self.write_func_name("LOG");
17730            self.write("(");
17731            self.generate_expression(&f.this)?;
17732            self.write(")");
17733        }
17734        Ok(())
17735    }
17736
17737    /// Whether the target dialect uses LOG(value, base) order (value first).
17738    /// BigQuery, TSQL, Tableau, Fabric use LOG(value, base).
17739    fn is_log_value_first(&self) -> bool {
17740        use crate::dialects::DialectType;
17741        matches!(
17742            self.config.dialect,
17743            Some(DialectType::BigQuery)
17744                | Some(DialectType::TSQL)
17745                | Some(DialectType::Tableau)
17746                | Some(DialectType::Fabric)
17747        )
17748    }
17749
17750    /// Whether the target dialect has LOG_BASE_FIRST = None (uses LOG2/LOG10 instead).
17751    /// Presto, Trino, ClickHouse, Athena.
17752    fn is_log_base_none(&self) -> bool {
17753        use crate::dialects::DialectType;
17754        matches!(
17755            self.config.dialect,
17756            Some(DialectType::Presto)
17757                | Some(DialectType::Trino)
17758                | Some(DialectType::ClickHouse)
17759                | Some(DialectType::Athena)
17760        )
17761    }
17762
17763    // Date/time function generators
17764
17765    fn generate_current_time(&mut self, f: &CurrentTime) -> Result<()> {
17766        self.write_keyword("CURRENT_TIME");
17767        if let Some(precision) = f.precision {
17768            self.write(&format!("({})", precision));
17769        }
17770        Ok(())
17771    }
17772
17773    fn generate_current_timestamp(&mut self, f: &CurrentTimestamp) -> Result<()> {
17774        use crate::dialects::DialectType;
17775
17776        // Oracle/Redshift SYSDATE handling
17777        if f.sysdate {
17778            match self.config.dialect {
17779                Some(DialectType::Oracle) | Some(DialectType::Redshift) => {
17780                    self.write_keyword("SYSDATE");
17781                    return Ok(());
17782                }
17783                Some(DialectType::Snowflake) => {
17784                    // Snowflake uses SYSDATE() function
17785                    self.write_keyword("SYSDATE");
17786                    self.write("()");
17787                    return Ok(());
17788                }
17789                _ => {
17790                    // Other dialects use CURRENT_TIMESTAMP for SYSDATE
17791                }
17792            }
17793        }
17794
17795        self.write_keyword("CURRENT_TIMESTAMP");
17796        // MySQL, Spark, Hive always use CURRENT_TIMESTAMP() with parentheses
17797        if let Some(precision) = f.precision {
17798            self.write(&format!("({})", precision));
17799        } else if matches!(
17800            self.config.dialect,
17801            Some(crate::dialects::DialectType::MySQL)
17802                | Some(crate::dialects::DialectType::SingleStore)
17803                | Some(crate::dialects::DialectType::TiDB)
17804                | Some(crate::dialects::DialectType::Spark)
17805                | Some(crate::dialects::DialectType::Hive)
17806                | Some(crate::dialects::DialectType::Databricks)
17807                | Some(crate::dialects::DialectType::ClickHouse)
17808                | Some(crate::dialects::DialectType::BigQuery)
17809                | Some(crate::dialects::DialectType::Snowflake)
17810        ) {
17811            self.write("()");
17812        }
17813        Ok(())
17814    }
17815
17816    fn generate_at_time_zone(&mut self, f: &AtTimeZone) -> Result<()> {
17817        // Exasol uses CONVERT_TZ(timestamp, 'UTC', zone) instead of AT TIME ZONE
17818        if self.config.dialect == Some(DialectType::Exasol) {
17819            self.write_keyword("CONVERT_TZ");
17820            self.write("(");
17821            self.generate_expression(&f.this)?;
17822            self.write(", 'UTC', ");
17823            self.generate_expression(&f.zone)?;
17824            self.write(")");
17825            return Ok(());
17826        }
17827
17828        self.generate_expression(&f.this)?;
17829        self.write_space();
17830        self.write_keyword("AT TIME ZONE");
17831        self.write_space();
17832        self.generate_expression(&f.zone)?;
17833        Ok(())
17834    }
17835
17836    fn generate_date_add(&mut self, f: &DateAddFunc, name: &str) -> Result<()> {
17837        use crate::dialects::DialectType;
17838
17839        // Presto/Trino use DATE_ADD('unit', interval, date) format
17840        // with the interval cast to BIGINT when needed
17841        let is_presto_like = matches!(
17842            self.config.dialect,
17843            Some(DialectType::Presto) | Some(DialectType::Trino)
17844        );
17845
17846        if is_presto_like {
17847            self.write_keyword(name);
17848            self.write("(");
17849            // Unit as string literal
17850            self.write("'");
17851            self.write_simple_interval_unit(&f.unit, false);
17852            self.write("'");
17853            self.write(", ");
17854            // Interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
17855            let needs_cast = !self.returns_integer_type(&f.interval);
17856            if needs_cast {
17857                self.write_keyword("CAST");
17858                self.write("(");
17859            }
17860            self.generate_expression(&f.interval)?;
17861            if needs_cast {
17862                self.write_space();
17863                self.write_keyword("AS");
17864                self.write_space();
17865                self.write_keyword("BIGINT");
17866                self.write(")");
17867            }
17868            self.write(", ");
17869            self.generate_expression(&f.this)?;
17870            self.write(")");
17871        } else {
17872            self.write_keyword(name);
17873            self.write("(");
17874            self.generate_expression(&f.this)?;
17875            self.write(", ");
17876            self.write_keyword("INTERVAL");
17877            self.write_space();
17878            self.generate_expression(&f.interval)?;
17879            self.write_space();
17880            self.write_simple_interval_unit(&f.unit, false); // Use singular form for DATEADD
17881            self.write(")");
17882        }
17883        Ok(())
17884    }
17885
17886    /// Check if an expression returns an integer type (doesn't need cast to BIGINT in Presto DATE_ADD)
17887    /// This is a heuristic to avoid full type inference
17888    fn returns_integer_type(&self, expr: &Expression) -> bool {
17889        use crate::expressions::{DataType, Literal};
17890        match expr {
17891            // Integer literals (no decimal point)
17892            Expression::Literal(Literal::Number(n)) => !n.contains('.'),
17893
17894            // FLOOR(x) returns integer if x is integer
17895            Expression::Floor(f) => self.returns_integer_type(&f.this),
17896
17897            // ROUND(x) returns integer if x is integer
17898            Expression::Round(f) => {
17899                // Only if no decimals arg or it's returning an integer
17900                f.decimals.is_none() && self.returns_integer_type(&f.this)
17901            }
17902
17903            // SIGN returns integer if input is integer
17904            Expression::Sign(f) => self.returns_integer_type(&f.this),
17905
17906            // ABS returns the same type as input
17907            Expression::Abs(f) => self.returns_integer_type(&f.this),
17908
17909            // Arithmetic operations on integers return integers
17910            Expression::Mul(op) => {
17911                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
17912            }
17913            Expression::Add(op) => {
17914                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
17915            }
17916            Expression::Sub(op) => {
17917                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
17918            }
17919            Expression::Mod(op) => self.returns_integer_type(&op.left),
17920
17921            // CAST(x AS BIGINT/INT/INTEGER/SMALLINT/TINYINT) returns integer
17922            Expression::Cast(c) => matches!(
17923                &c.to,
17924                DataType::BigInt { .. }
17925                    | DataType::Int { .. }
17926                    | DataType::SmallInt { .. }
17927                    | DataType::TinyInt { .. }
17928            ),
17929
17930            // Negation: -x returns integer if x is integer
17931            Expression::Neg(op) => self.returns_integer_type(&op.this),
17932
17933            // Parenthesized expression
17934            Expression::Paren(p) => self.returns_integer_type(&p.this),
17935
17936            // Column references and most expressions are assumed to need casting
17937            // since we don't have full type information
17938            _ => false,
17939        }
17940    }
17941
17942    fn generate_datediff(&mut self, f: &DateDiffFunc) -> Result<()> {
17943        self.write_keyword("DATEDIFF");
17944        self.write("(");
17945        if let Some(unit) = &f.unit {
17946            self.write_simple_interval_unit(unit, false); // Use singular form for DATEDIFF
17947            self.write(", ");
17948        }
17949        self.generate_expression(&f.this)?;
17950        self.write(", ");
17951        self.generate_expression(&f.expression)?;
17952        self.write(")");
17953        Ok(())
17954    }
17955
17956    fn generate_date_trunc(&mut self, f: &DateTruncFunc) -> Result<()> {
17957        self.write_keyword("DATE_TRUNC");
17958        self.write("('");
17959        self.write_datetime_field(&f.unit);
17960        self.write("', ");
17961        self.generate_expression(&f.this)?;
17962        self.write(")");
17963        Ok(())
17964    }
17965
17966    fn generate_last_day(&mut self, f: &LastDayFunc) -> Result<()> {
17967        use crate::dialects::DialectType;
17968        use crate::expressions::DateTimeField;
17969
17970        self.write_keyword("LAST_DAY");
17971        self.write("(");
17972        self.generate_expression(&f.this)?;
17973        if let Some(unit) = &f.unit {
17974            self.write(", ");
17975            // BigQuery: strip week-start modifier from WEEK(SUNDAY), WEEK(MONDAY), etc.
17976            // WEEK(SUNDAY) -> WEEK
17977            if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
17978                if let DateTimeField::WeekWithModifier(_) = unit {
17979                    self.write_keyword("WEEK");
17980                } else {
17981                    self.write_datetime_field(unit);
17982                }
17983            } else {
17984                self.write_datetime_field(unit);
17985            }
17986        }
17987        self.write(")");
17988        Ok(())
17989    }
17990
17991    fn generate_extract(&mut self, f: &ExtractFunc) -> Result<()> {
17992        // TSQL/Fabric use DATEPART(part, expr) instead of EXTRACT(part FROM expr)
17993        if matches!(
17994            self.config.dialect,
17995            Some(DialectType::TSQL) | Some(DialectType::Fabric)
17996        ) {
17997            self.write_keyword("DATEPART");
17998            self.write("(");
17999            self.write_datetime_field(&f.field);
18000            self.write(", ");
18001            self.generate_expression(&f.this)?;
18002            self.write(")");
18003            return Ok(());
18004        }
18005        self.write_keyword("EXTRACT");
18006        self.write("(");
18007        // Hive/Spark use lowercase datetime fields in EXTRACT
18008        if matches!(
18009            self.config.dialect,
18010            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
18011        ) {
18012            self.write_datetime_field_lower(&f.field);
18013        } else {
18014            self.write_datetime_field(&f.field);
18015        }
18016        self.write_space();
18017        self.write_keyword("FROM");
18018        self.write_space();
18019        self.generate_expression(&f.this)?;
18020        self.write(")");
18021        Ok(())
18022    }
18023
18024    fn generate_to_date(&mut self, f: &ToDateFunc) -> Result<()> {
18025        self.write_keyword("TO_DATE");
18026        self.write("(");
18027        self.generate_expression(&f.this)?;
18028        if let Some(format) = &f.format {
18029            self.write(", ");
18030            self.generate_expression(format)?;
18031        }
18032        self.write(")");
18033        Ok(())
18034    }
18035
18036    fn generate_to_timestamp(&mut self, f: &ToTimestampFunc) -> Result<()> {
18037        self.write_keyword("TO_TIMESTAMP");
18038        self.write("(");
18039        self.generate_expression(&f.this)?;
18040        if let Some(format) = &f.format {
18041            self.write(", ");
18042            self.generate_expression(format)?;
18043        }
18044        self.write(")");
18045        Ok(())
18046    }
18047
18048    // Control flow function generators
18049
18050    fn generate_if_func(&mut self, f: &IfFunc) -> Result<()> {
18051        use crate::dialects::DialectType;
18052
18053        // Generic mode: normalize IF to CASE WHEN
18054        if self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic) {
18055            self.write_keyword("CASE WHEN");
18056            self.write_space();
18057            self.generate_expression(&f.condition)?;
18058            self.write_space();
18059            self.write_keyword("THEN");
18060            self.write_space();
18061            self.generate_expression(&f.true_value)?;
18062            if let Some(false_val) = &f.false_value {
18063                self.write_space();
18064                self.write_keyword("ELSE");
18065                self.write_space();
18066                self.generate_expression(false_val)?;
18067            }
18068            self.write_space();
18069            self.write_keyword("END");
18070            return Ok(());
18071        }
18072
18073        // Exasol uses IF condition THEN true_value ELSE false_value ENDIF syntax
18074        if self.config.dialect == Some(DialectType::Exasol) {
18075            self.write_keyword("IF");
18076            self.write_space();
18077            self.generate_expression(&f.condition)?;
18078            self.write_space();
18079            self.write_keyword("THEN");
18080            self.write_space();
18081            self.generate_expression(&f.true_value)?;
18082            if let Some(false_val) = &f.false_value {
18083                self.write_space();
18084                self.write_keyword("ELSE");
18085                self.write_space();
18086                self.generate_expression(false_val)?;
18087            }
18088            self.write_space();
18089            self.write_keyword("ENDIF");
18090            return Ok(());
18091        }
18092
18093        // Choose function name based on target dialect
18094        let func_name = match self.config.dialect {
18095            Some(DialectType::Snowflake) => "IFF",
18096            Some(DialectType::SQLite) | Some(DialectType::TSQL) => "IIF",
18097            Some(DialectType::Drill) => "`IF`",
18098            _ => "IF",
18099        };
18100        self.write(func_name);
18101        self.write("(");
18102        self.generate_expression(&f.condition)?;
18103        self.write(", ");
18104        self.generate_expression(&f.true_value)?;
18105        if let Some(false_val) = &f.false_value {
18106            self.write(", ");
18107            self.generate_expression(false_val)?;
18108        }
18109        self.write(")");
18110        Ok(())
18111    }
18112
18113    fn generate_nvl2(&mut self, f: &Nvl2Func) -> Result<()> {
18114        self.write_keyword("NVL2");
18115        self.write("(");
18116        self.generate_expression(&f.this)?;
18117        self.write(", ");
18118        self.generate_expression(&f.true_value)?;
18119        self.write(", ");
18120        self.generate_expression(&f.false_value)?;
18121        self.write(")");
18122        Ok(())
18123    }
18124
18125    // Typed aggregate function generators
18126
18127    fn generate_count(&mut self, f: &CountFunc) -> Result<()> {
18128        // Use normalize_functions for COUNT to respect ClickHouse case preservation
18129        let count_name = match self.config.normalize_functions {
18130            NormalizeFunctions::Upper => "COUNT".to_string(),
18131            NormalizeFunctions::Lower => "count".to_string(),
18132            NormalizeFunctions::None => f
18133                .original_name
18134                .clone()
18135                .unwrap_or_else(|| "COUNT".to_string()),
18136        };
18137        self.write(&count_name);
18138        self.write("(");
18139        if f.distinct {
18140            self.write_keyword("DISTINCT");
18141            self.write_space();
18142        }
18143        if f.star {
18144            self.write("*");
18145        } else if let Some(ref expr) = f.this {
18146            // For COUNT(DISTINCT a, b), unwrap the Tuple to avoid extra parentheses
18147            if let Expression::Tuple(tuple) = expr {
18148                // Check if we need to transform multi-arg COUNT DISTINCT
18149                // When dialect doesn't support multi_arg_distinct, transform:
18150                // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
18151                let needs_transform =
18152                    f.distinct && tuple.expressions.len() > 1 && !self.config.multi_arg_distinct;
18153
18154                if needs_transform {
18155                    // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
18156                    self.write_keyword("CASE");
18157                    for e in &tuple.expressions {
18158                        self.write_space();
18159                        self.write_keyword("WHEN");
18160                        self.write_space();
18161                        self.generate_expression(e)?;
18162                        self.write_space();
18163                        self.write_keyword("IS NULL THEN NULL");
18164                    }
18165                    self.write_space();
18166                    self.write_keyword("ELSE");
18167                    self.write(" (");
18168                    for (i, e) in tuple.expressions.iter().enumerate() {
18169                        if i > 0 {
18170                            self.write(", ");
18171                        }
18172                        self.generate_expression(e)?;
18173                    }
18174                    self.write(")");
18175                    self.write_space();
18176                    self.write_keyword("END");
18177                } else {
18178                    for (i, e) in tuple.expressions.iter().enumerate() {
18179                        if i > 0 {
18180                            self.write(", ");
18181                        }
18182                        self.generate_expression(e)?;
18183                    }
18184                }
18185            } else {
18186                self.generate_expression(expr)?;
18187            }
18188        }
18189        // RESPECT NULLS / IGNORE NULLS
18190        if let Some(ignore) = f.ignore_nulls {
18191            self.write_space();
18192            if ignore {
18193                self.write_keyword("IGNORE NULLS");
18194            } else {
18195                self.write_keyword("RESPECT NULLS");
18196            }
18197        }
18198        self.write(")");
18199        if let Some(ref filter) = f.filter {
18200            self.write_space();
18201            self.write_keyword("FILTER");
18202            self.write("(");
18203            self.write_keyword("WHERE");
18204            self.write_space();
18205            self.generate_expression(filter)?;
18206            self.write(")");
18207        }
18208        Ok(())
18209    }
18210
18211    fn generate_agg_func(&mut self, name: &str, f: &AggFunc) -> Result<()> {
18212        // Apply function name normalization based on config
18213        let func_name = match self.config.normalize_functions {
18214            NormalizeFunctions::Upper => name.to_uppercase(),
18215            NormalizeFunctions::Lower => name.to_lowercase(),
18216            NormalizeFunctions::None => {
18217                // Use the original function name from parsing if available,
18218                // otherwise fall back to lowercase of the hardcoded constant
18219                if let Some(ref original) = f.name {
18220                    original.clone()
18221                } else {
18222                    name.to_lowercase()
18223                }
18224            }
18225        };
18226        self.write(&func_name);
18227        self.write("(");
18228        if f.distinct {
18229            self.write_keyword("DISTINCT");
18230            self.write_space();
18231        }
18232        // Skip generating the expression if it's a NULL placeholder for zero-arg aggregates like MODE()
18233        if !matches!(f.this, Expression::Null(_)) {
18234            self.generate_expression(&f.this)?;
18235        }
18236        // Generate IGNORE NULLS / RESPECT NULLS inside parens if config says so (BigQuery style)
18237        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
18238        if self.config.ignore_nulls_in_func
18239            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
18240        {
18241            match f.ignore_nulls {
18242                Some(true) => {
18243                    self.write_space();
18244                    self.write_keyword("IGNORE NULLS");
18245                }
18246                Some(false) => {
18247                    self.write_space();
18248                    self.write_keyword("RESPECT NULLS");
18249                }
18250                None => {}
18251            }
18252        }
18253        // Generate HAVING MAX/MIN if present (BigQuery syntax)
18254        // e.g., ANY_VALUE(fruit HAVING MAX sold)
18255        if let Some((ref expr, is_max)) = f.having_max {
18256            self.write_space();
18257            self.write_keyword("HAVING");
18258            self.write_space();
18259            if is_max {
18260                self.write_keyword("MAX");
18261            } else {
18262                self.write_keyword("MIN");
18263            }
18264            self.write_space();
18265            self.generate_expression(expr)?;
18266        }
18267        // Generate ORDER BY if present (for aggregates like ARRAY_AGG(x ORDER BY y))
18268        if !f.order_by.is_empty() {
18269            self.write_space();
18270            self.write_keyword("ORDER BY");
18271            self.write_space();
18272            for (i, ord) in f.order_by.iter().enumerate() {
18273                if i > 0 {
18274                    self.write(", ");
18275                }
18276                self.generate_ordered(ord)?;
18277            }
18278        }
18279        // Generate LIMIT if present (for aggregates like ARRAY_AGG(x ORDER BY y LIMIT 2))
18280        if let Some(ref limit) = f.limit {
18281            self.write_space();
18282            self.write_keyword("LIMIT");
18283            self.write_space();
18284            // Check if this is a Tuple representing LIMIT offset, count
18285            if let Expression::Tuple(t) = limit.as_ref() {
18286                if t.expressions.len() == 2 {
18287                    self.generate_expression(&t.expressions[0])?;
18288                    self.write(", ");
18289                    self.generate_expression(&t.expressions[1])?;
18290                } else {
18291                    self.generate_expression(limit)?;
18292                }
18293            } else {
18294                self.generate_expression(limit)?;
18295            }
18296        }
18297        self.write(")");
18298        // Generate IGNORE NULLS / RESPECT NULLS outside parens if config says so (standard style)
18299        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
18300        if !self.config.ignore_nulls_in_func
18301            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
18302        {
18303            match f.ignore_nulls {
18304                Some(true) => {
18305                    self.write_space();
18306                    self.write_keyword("IGNORE NULLS");
18307                }
18308                Some(false) => {
18309                    self.write_space();
18310                    self.write_keyword("RESPECT NULLS");
18311                }
18312                None => {}
18313            }
18314        }
18315        if let Some(ref filter) = f.filter {
18316            self.write_space();
18317            self.write_keyword("FILTER");
18318            self.write("(");
18319            self.write_keyword("WHERE");
18320            self.write_space();
18321            self.generate_expression(filter)?;
18322            self.write(")");
18323        }
18324        Ok(())
18325    }
18326
18327    fn generate_group_concat(&mut self, f: &GroupConcatFunc) -> Result<()> {
18328        self.write_keyword("GROUP_CONCAT");
18329        self.write("(");
18330        if f.distinct {
18331            self.write_keyword("DISTINCT");
18332            self.write_space();
18333        }
18334        self.generate_expression(&f.this)?;
18335        if let Some(ref order_by) = f.order_by {
18336            self.write_space();
18337            self.write_keyword("ORDER BY");
18338            self.write_space();
18339            for (i, ord) in order_by.iter().enumerate() {
18340                if i > 0 {
18341                    self.write(", ");
18342                }
18343                self.generate_ordered(ord)?;
18344            }
18345        }
18346        if let Some(ref sep) = f.separator {
18347            // SQLite uses GROUP_CONCAT(x, sep) syntax (comma-separated)
18348            // MySQL and others use GROUP_CONCAT(x SEPARATOR sep) syntax
18349            if matches!(
18350                self.config.dialect,
18351                Some(crate::dialects::DialectType::SQLite)
18352            ) {
18353                self.write(", ");
18354                self.generate_expression(sep)?;
18355            } else {
18356                self.write_space();
18357                self.write_keyword("SEPARATOR");
18358                self.write_space();
18359                self.generate_expression(sep)?;
18360            }
18361        }
18362        self.write(")");
18363        if let Some(ref filter) = f.filter {
18364            self.write_space();
18365            self.write_keyword("FILTER");
18366            self.write("(");
18367            self.write_keyword("WHERE");
18368            self.write_space();
18369            self.generate_expression(filter)?;
18370            self.write(")");
18371        }
18372        Ok(())
18373    }
18374
18375    fn generate_string_agg(&mut self, f: &StringAggFunc) -> Result<()> {
18376        let is_tsql = matches!(
18377            self.config.dialect,
18378            Some(crate::dialects::DialectType::TSQL)
18379        );
18380        self.write_keyword("STRING_AGG");
18381        self.write("(");
18382        if f.distinct {
18383            self.write_keyword("DISTINCT");
18384            self.write_space();
18385        }
18386        self.generate_expression(&f.this)?;
18387        if let Some(ref separator) = f.separator {
18388            self.write(", ");
18389            self.generate_expression(separator)?;
18390        }
18391        // For TSQL, ORDER BY goes in WITHIN GROUP clause after the closing paren
18392        if !is_tsql {
18393            if let Some(ref order_by) = f.order_by {
18394                self.write_space();
18395                self.write_keyword("ORDER BY");
18396                self.write_space();
18397                for (i, ord) in order_by.iter().enumerate() {
18398                    if i > 0 {
18399                        self.write(", ");
18400                    }
18401                    self.generate_ordered(ord)?;
18402                }
18403            }
18404        }
18405        if let Some(ref limit) = f.limit {
18406            self.write_space();
18407            self.write_keyword("LIMIT");
18408            self.write_space();
18409            self.generate_expression(limit)?;
18410        }
18411        self.write(")");
18412        // TSQL uses WITHIN GROUP (ORDER BY ...) after the function call
18413        if is_tsql {
18414            if let Some(ref order_by) = f.order_by {
18415                self.write_space();
18416                self.write_keyword("WITHIN GROUP");
18417                self.write(" (");
18418                self.write_keyword("ORDER BY");
18419                self.write_space();
18420                for (i, ord) in order_by.iter().enumerate() {
18421                    if i > 0 {
18422                        self.write(", ");
18423                    }
18424                    self.generate_ordered(ord)?;
18425                }
18426                self.write(")");
18427            }
18428        }
18429        if let Some(ref filter) = f.filter {
18430            self.write_space();
18431            self.write_keyword("FILTER");
18432            self.write("(");
18433            self.write_keyword("WHERE");
18434            self.write_space();
18435            self.generate_expression(filter)?;
18436            self.write(")");
18437        }
18438        Ok(())
18439    }
18440
18441    fn generate_listagg(&mut self, f: &ListAggFunc) -> Result<()> {
18442        use crate::dialects::DialectType;
18443        self.write_keyword("LISTAGG");
18444        self.write("(");
18445        if f.distinct {
18446            self.write_keyword("DISTINCT");
18447            self.write_space();
18448        }
18449        self.generate_expression(&f.this)?;
18450        if let Some(ref sep) = f.separator {
18451            self.write(", ");
18452            self.generate_expression(sep)?;
18453        } else if matches!(
18454            self.config.dialect,
18455            Some(DialectType::Trino) | Some(DialectType::Presto)
18456        ) {
18457            // Trino/Presto require explicit separator; default to ','
18458            self.write(", ','");
18459        }
18460        if let Some(ref overflow) = f.on_overflow {
18461            self.write_space();
18462            self.write_keyword("ON OVERFLOW");
18463            self.write_space();
18464            match overflow {
18465                ListAggOverflow::Error => self.write_keyword("ERROR"),
18466                ListAggOverflow::Truncate { filler, with_count } => {
18467                    self.write_keyword("TRUNCATE");
18468                    if let Some(ref fill) = filler {
18469                        self.write_space();
18470                        self.generate_expression(fill)?;
18471                    }
18472                    if *with_count {
18473                        self.write_space();
18474                        self.write_keyword("WITH COUNT");
18475                    } else {
18476                        self.write_space();
18477                        self.write_keyword("WITHOUT COUNT");
18478                    }
18479                }
18480            }
18481        }
18482        self.write(")");
18483        if let Some(ref order_by) = f.order_by {
18484            self.write_space();
18485            self.write_keyword("WITHIN GROUP");
18486            self.write(" (");
18487            self.write_keyword("ORDER BY");
18488            self.write_space();
18489            for (i, ord) in order_by.iter().enumerate() {
18490                if i > 0 {
18491                    self.write(", ");
18492                }
18493                self.generate_ordered(ord)?;
18494            }
18495            self.write(")");
18496        }
18497        if let Some(ref filter) = f.filter {
18498            self.write_space();
18499            self.write_keyword("FILTER");
18500            self.write("(");
18501            self.write_keyword("WHERE");
18502            self.write_space();
18503            self.generate_expression(filter)?;
18504            self.write(")");
18505        }
18506        Ok(())
18507    }
18508
18509    fn generate_sum_if(&mut self, f: &SumIfFunc) -> Result<()> {
18510        self.write_keyword("SUM_IF");
18511        self.write("(");
18512        self.generate_expression(&f.this)?;
18513        self.write(", ");
18514        self.generate_expression(&f.condition)?;
18515        self.write(")");
18516        if let Some(ref filter) = f.filter {
18517            self.write_space();
18518            self.write_keyword("FILTER");
18519            self.write("(");
18520            self.write_keyword("WHERE");
18521            self.write_space();
18522            self.generate_expression(filter)?;
18523            self.write(")");
18524        }
18525        Ok(())
18526    }
18527
18528    fn generate_approx_percentile(&mut self, f: &ApproxPercentileFunc) -> Result<()> {
18529        self.write_keyword("APPROX_PERCENTILE");
18530        self.write("(");
18531        self.generate_expression(&f.this)?;
18532        self.write(", ");
18533        self.generate_expression(&f.percentile)?;
18534        if let Some(ref acc) = f.accuracy {
18535            self.write(", ");
18536            self.generate_expression(acc)?;
18537        }
18538        self.write(")");
18539        if let Some(ref filter) = f.filter {
18540            self.write_space();
18541            self.write_keyword("FILTER");
18542            self.write("(");
18543            self.write_keyword("WHERE");
18544            self.write_space();
18545            self.generate_expression(filter)?;
18546            self.write(")");
18547        }
18548        Ok(())
18549    }
18550
18551    fn generate_percentile(&mut self, name: &str, f: &PercentileFunc) -> Result<()> {
18552        self.write_keyword(name);
18553        self.write("(");
18554        self.generate_expression(&f.percentile)?;
18555        self.write(")");
18556        if let Some(ref order_by) = f.order_by {
18557            self.write_space();
18558            self.write_keyword("WITHIN GROUP");
18559            self.write(" (");
18560            self.write_keyword("ORDER BY");
18561            self.write_space();
18562            self.generate_expression(&f.this)?;
18563            for ord in order_by.iter() {
18564                if ord.desc {
18565                    self.write_space();
18566                    self.write_keyword("DESC");
18567                }
18568            }
18569            self.write(")");
18570        }
18571        if let Some(ref filter) = f.filter {
18572            self.write_space();
18573            self.write_keyword("FILTER");
18574            self.write("(");
18575            self.write_keyword("WHERE");
18576            self.write_space();
18577            self.generate_expression(filter)?;
18578            self.write(")");
18579        }
18580        Ok(())
18581    }
18582
18583    // Window function generators
18584
18585    fn generate_ntile(&mut self, f: &NTileFunc) -> Result<()> {
18586        self.write_keyword("NTILE");
18587        self.write("(");
18588        if let Some(num_buckets) = &f.num_buckets {
18589            self.generate_expression(num_buckets)?;
18590        }
18591        if let Some(order_by) = &f.order_by {
18592            self.write_keyword(" ORDER BY ");
18593            for (i, ob) in order_by.iter().enumerate() {
18594                if i > 0 {
18595                    self.write(", ");
18596                }
18597                self.generate_ordered(ob)?;
18598            }
18599        }
18600        self.write(")");
18601        Ok(())
18602    }
18603
18604    fn generate_lead_lag(&mut self, name: &str, f: &LeadLagFunc) -> Result<()> {
18605        self.write_keyword(name);
18606        self.write("(");
18607        self.generate_expression(&f.this)?;
18608        if let Some(ref offset) = f.offset {
18609            self.write(", ");
18610            self.generate_expression(offset)?;
18611            if let Some(ref default) = f.default {
18612                self.write(", ");
18613                self.generate_expression(default)?;
18614            }
18615        }
18616        // IGNORE NULLS inside parens for dialects like BigQuery
18617        if f.ignore_nulls && self.config.ignore_nulls_in_func {
18618            self.write_space();
18619            self.write_keyword("IGNORE NULLS");
18620        }
18621        self.write(")");
18622        // IGNORE NULLS outside parens for other dialects
18623        if f.ignore_nulls && !self.config.ignore_nulls_in_func {
18624            self.write_space();
18625            self.write_keyword("IGNORE NULLS");
18626        }
18627        Ok(())
18628    }
18629
18630    fn generate_value_func(&mut self, name: &str, f: &ValueFunc) -> Result<()> {
18631        self.write_keyword(name);
18632        self.write("(");
18633        self.generate_expression(&f.this)?;
18634        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery
18635        if self.config.ignore_nulls_in_func {
18636            match f.ignore_nulls {
18637                Some(true) => {
18638                    self.write_space();
18639                    self.write_keyword("IGNORE NULLS");
18640                }
18641                Some(false) => {
18642                    self.write_space();
18643                    self.write_keyword("RESPECT NULLS");
18644                }
18645                None => {}
18646            }
18647        }
18648        self.write(")");
18649        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
18650        if !self.config.ignore_nulls_in_func {
18651            match f.ignore_nulls {
18652                Some(true) => {
18653                    self.write_space();
18654                    self.write_keyword("IGNORE NULLS");
18655                }
18656                Some(false) => {
18657                    self.write_space();
18658                    self.write_keyword("RESPECT NULLS");
18659                }
18660                None => {}
18661            }
18662        }
18663        Ok(())
18664    }
18665
18666    fn generate_nth_value(&mut self, f: &NthValueFunc) -> Result<()> {
18667        self.write_keyword("NTH_VALUE");
18668        self.write("(");
18669        self.generate_expression(&f.this)?;
18670        self.write(", ");
18671        self.generate_expression(&f.offset)?;
18672        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
18673        if self.config.ignore_nulls_in_func {
18674            match f.ignore_nulls {
18675                Some(true) => {
18676                    self.write_space();
18677                    self.write_keyword("IGNORE NULLS");
18678                }
18679                Some(false) => {
18680                    self.write_space();
18681                    self.write_keyword("RESPECT NULLS");
18682                }
18683                None => {}
18684            }
18685        }
18686        self.write(")");
18687        // FROM FIRST / FROM LAST (Snowflake-specific, before IGNORE/RESPECT NULLS)
18688        if matches!(
18689            self.config.dialect,
18690            Some(crate::dialects::DialectType::Snowflake)
18691        ) {
18692            match f.from_first {
18693                Some(true) => {
18694                    self.write_space();
18695                    self.write_keyword("FROM FIRST");
18696                }
18697                Some(false) => {
18698                    self.write_space();
18699                    self.write_keyword("FROM LAST");
18700                }
18701                None => {}
18702            }
18703        }
18704        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
18705        if !self.config.ignore_nulls_in_func {
18706            match f.ignore_nulls {
18707                Some(true) => {
18708                    self.write_space();
18709                    self.write_keyword("IGNORE NULLS");
18710                }
18711                Some(false) => {
18712                    self.write_space();
18713                    self.write_keyword("RESPECT NULLS");
18714                }
18715                None => {}
18716            }
18717        }
18718        Ok(())
18719    }
18720
18721    // Additional string function generators
18722
18723    fn generate_position(&mut self, f: &PositionFunc) -> Result<()> {
18724        // Standard syntax: POSITION(substr IN str)
18725        // ClickHouse prefers comma syntax with reversed arg order: POSITION(str, substr[, start])
18726        if matches!(
18727            self.config.dialect,
18728            Some(crate::dialects::DialectType::ClickHouse)
18729        ) {
18730            self.write_keyword("POSITION");
18731            self.write("(");
18732            self.generate_expression(&f.string)?;
18733            self.write(", ");
18734            self.generate_expression(&f.substring)?;
18735            if let Some(ref start) = f.start {
18736                self.write(", ");
18737                self.generate_expression(start)?;
18738            }
18739            self.write(")");
18740            return Ok(());
18741        }
18742
18743        self.write_keyword("POSITION");
18744        self.write("(");
18745        self.generate_expression(&f.substring)?;
18746        self.write_space();
18747        self.write_keyword("IN");
18748        self.write_space();
18749        self.generate_expression(&f.string)?;
18750        if let Some(ref start) = f.start {
18751            self.write(", ");
18752            self.generate_expression(start)?;
18753        }
18754        self.write(")");
18755        Ok(())
18756    }
18757
18758    // Additional math function generators
18759
18760    fn generate_rand(&mut self, f: &Rand) -> Result<()> {
18761        // Teradata RANDOM(lower, upper)
18762        if f.lower.is_some() || f.upper.is_some() {
18763            self.write_keyword("RANDOM");
18764            self.write("(");
18765            if let Some(ref lower) = f.lower {
18766                self.generate_expression(lower)?;
18767            }
18768            if let Some(ref upper) = f.upper {
18769                self.write(", ");
18770                self.generate_expression(upper)?;
18771            }
18772            self.write(")");
18773            return Ok(());
18774        }
18775        // Snowflake uses RANDOM instead of RAND, DuckDB uses RANDOM without seed
18776        let func_name = match self.config.dialect {
18777            Some(crate::dialects::DialectType::Snowflake)
18778            | Some(crate::dialects::DialectType::DuckDB) => "RANDOM",
18779            _ => "RAND",
18780        };
18781        self.write_keyword(func_name);
18782        self.write("(");
18783        // DuckDB doesn't support seeded RANDOM, so skip the seed
18784        if !matches!(
18785            self.config.dialect,
18786            Some(crate::dialects::DialectType::DuckDB)
18787        ) {
18788            if let Some(ref seed) = f.seed {
18789                self.generate_expression(seed)?;
18790            }
18791        }
18792        self.write(")");
18793        Ok(())
18794    }
18795
18796    fn generate_truncate_func(&mut self, f: &TruncateFunc) -> Result<()> {
18797        self.write_keyword("TRUNCATE");
18798        self.write("(");
18799        self.generate_expression(&f.this)?;
18800        if let Some(ref decimals) = f.decimals {
18801            self.write(", ");
18802            self.generate_expression(decimals)?;
18803        }
18804        self.write(")");
18805        Ok(())
18806    }
18807
18808    // Control flow generators
18809
18810    fn generate_decode(&mut self, f: &DecodeFunc) -> Result<()> {
18811        self.write_keyword("DECODE");
18812        self.write("(");
18813        self.generate_expression(&f.this)?;
18814        for (search, result) in &f.search_results {
18815            self.write(", ");
18816            self.generate_expression(search)?;
18817            self.write(", ");
18818            self.generate_expression(result)?;
18819        }
18820        if let Some(ref default) = f.default {
18821            self.write(", ");
18822            self.generate_expression(default)?;
18823        }
18824        self.write(")");
18825        Ok(())
18826    }
18827
18828    // Date/time function generators
18829
18830    fn generate_date_format(&mut self, name: &str, f: &DateFormatFunc) -> Result<()> {
18831        self.write_keyword(name);
18832        self.write("(");
18833        self.generate_expression(&f.this)?;
18834        self.write(", ");
18835        self.generate_expression(&f.format)?;
18836        self.write(")");
18837        Ok(())
18838    }
18839
18840    fn generate_from_unixtime(&mut self, f: &FromUnixtimeFunc) -> Result<()> {
18841        self.write_keyword("FROM_UNIXTIME");
18842        self.write("(");
18843        self.generate_expression(&f.this)?;
18844        if let Some(ref format) = f.format {
18845            self.write(", ");
18846            self.generate_expression(format)?;
18847        }
18848        self.write(")");
18849        Ok(())
18850    }
18851
18852    fn generate_unix_timestamp(&mut self, f: &UnixTimestampFunc) -> Result<()> {
18853        self.write_keyword("UNIX_TIMESTAMP");
18854        self.write("(");
18855        if let Some(ref expr) = f.this {
18856            self.generate_expression(expr)?;
18857            if let Some(ref format) = f.format {
18858                self.write(", ");
18859                self.generate_expression(format)?;
18860            }
18861        } else if matches!(
18862            self.config.dialect,
18863            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
18864        ) {
18865            // Spark/Hive: UNIX_TIMESTAMP() -> UNIX_TIMESTAMP(CURRENT_TIMESTAMP())
18866            self.write_keyword("CURRENT_TIMESTAMP");
18867            self.write("()");
18868        }
18869        self.write(")");
18870        Ok(())
18871    }
18872
18873    fn generate_make_date(&mut self, f: &MakeDateFunc) -> Result<()> {
18874        self.write_keyword("MAKE_DATE");
18875        self.write("(");
18876        self.generate_expression(&f.year)?;
18877        self.write(", ");
18878        self.generate_expression(&f.month)?;
18879        self.write(", ");
18880        self.generate_expression(&f.day)?;
18881        self.write(")");
18882        Ok(())
18883    }
18884
18885    fn generate_make_timestamp(&mut self, f: &MakeTimestampFunc) -> Result<()> {
18886        self.write_keyword("MAKE_TIMESTAMP");
18887        self.write("(");
18888        self.generate_expression(&f.year)?;
18889        self.write(", ");
18890        self.generate_expression(&f.month)?;
18891        self.write(", ");
18892        self.generate_expression(&f.day)?;
18893        self.write(", ");
18894        self.generate_expression(&f.hour)?;
18895        self.write(", ");
18896        self.generate_expression(&f.minute)?;
18897        self.write(", ");
18898        self.generate_expression(&f.second)?;
18899        if let Some(ref tz) = f.timezone {
18900            self.write(", ");
18901            self.generate_expression(tz)?;
18902        }
18903        self.write(")");
18904        Ok(())
18905    }
18906
18907    /// Extract field names from a struct expression (either Struct or Function named STRUCT with Alias args)
18908    fn extract_struct_field_names(expr: &Expression) -> Option<Vec<String>> {
18909        match expr {
18910            Expression::Struct(s) => {
18911                if s.fields.iter().all(|(name, _)| name.is_some()) {
18912                    Some(
18913                        s.fields
18914                            .iter()
18915                            .map(|(name, _)| name.as_deref().unwrap_or("").to_string())
18916                            .collect(),
18917                    )
18918                } else {
18919                    None
18920                }
18921            }
18922            Expression::Function(f) if f.name.to_uppercase() == "STRUCT" => {
18923                // Check if all args are Alias (named fields)
18924                if f.args.iter().all(|a| matches!(a, Expression::Alias(_))) {
18925                    Some(
18926                        f.args
18927                            .iter()
18928                            .filter_map(|a| {
18929                                if let Expression::Alias(alias) = a {
18930                                    Some(alias.alias.name.clone())
18931                                } else {
18932                                    None
18933                                }
18934                            })
18935                            .collect(),
18936                    )
18937                } else {
18938                    None
18939                }
18940            }
18941            _ => None,
18942        }
18943    }
18944
18945    /// Check if a struct expression has any unnamed fields
18946    fn struct_has_unnamed_fields(expr: &Expression) -> bool {
18947        match expr {
18948            Expression::Struct(s) => s.fields.iter().any(|(name, _)| name.is_none()),
18949            Expression::Function(f) if f.name.to_uppercase() == "STRUCT" => {
18950                f.args.iter().any(|a| !matches!(a, Expression::Alias(_)))
18951            }
18952            _ => false,
18953        }
18954    }
18955
18956    /// Get the field count of a struct expression
18957    fn struct_field_count(expr: &Expression) -> usize {
18958        match expr {
18959            Expression::Struct(s) => s.fields.len(),
18960            Expression::Function(f) if f.name.to_uppercase() == "STRUCT" => f.args.len(),
18961            _ => 0,
18962        }
18963    }
18964
18965    /// Apply field names to an unnamed struct expression, producing a new expression with names
18966    fn apply_struct_field_names(expr: &Expression, field_names: &[String]) -> Expression {
18967        match expr {
18968            Expression::Struct(s) => {
18969                let mut new_fields = Vec::with_capacity(s.fields.len());
18970                for (i, (name, value)) in s.fields.iter().enumerate() {
18971                    if name.is_none() && i < field_names.len() {
18972                        new_fields.push((Some(field_names[i].clone()), value.clone()));
18973                    } else {
18974                        new_fields.push((name.clone(), value.clone()));
18975                    }
18976                }
18977                Expression::Struct(Box::new(crate::expressions::Struct { fields: new_fields }))
18978            }
18979            Expression::Function(f) if f.name.to_uppercase() == "STRUCT" => {
18980                let mut new_args = Vec::with_capacity(f.args.len());
18981                for (i, arg) in f.args.iter().enumerate() {
18982                    if !matches!(arg, Expression::Alias(_)) && i < field_names.len() {
18983                        // Wrap the value in an Alias with the inherited name
18984                        new_args.push(Expression::Alias(Box::new(crate::expressions::Alias {
18985                            this: arg.clone(),
18986                            alias: crate::expressions::Identifier::new(field_names[i].clone()),
18987                            column_aliases: Vec::new(),
18988                            pre_alias_comments: Vec::new(),
18989                            trailing_comments: Vec::new(),
18990                        })));
18991                    } else {
18992                        new_args.push(arg.clone());
18993                    }
18994                }
18995                Expression::Function(Box::new(crate::expressions::Function {
18996                    name: f.name.clone(),
18997                    args: new_args,
18998                    distinct: f.distinct,
18999                    trailing_comments: f.trailing_comments.clone(),
19000                    use_bracket_syntax: f.use_bracket_syntax,
19001                    no_parens: f.no_parens,
19002                    quoted: f.quoted,
19003                }))
19004            }
19005            _ => expr.clone(),
19006        }
19007    }
19008
19009    /// Propagate struct field names from the first struct in an array to subsequent unnamed structs.
19010    /// This implements BigQuery's implicit field name inheritance for struct arrays.
19011    /// Handles both Expression::Struct and Expression::Function named "STRUCT".
19012    fn inherit_struct_field_names(expressions: &[Expression]) -> Vec<Expression> {
19013        let first = match expressions.first() {
19014            Some(e) => e,
19015            None => return expressions.to_vec(),
19016        };
19017
19018        let field_names = match Self::extract_struct_field_names(first) {
19019            Some(names) if !names.is_empty() => names,
19020            _ => return expressions.to_vec(),
19021        };
19022
19023        let mut result = Vec::with_capacity(expressions.len());
19024        for (idx, expr) in expressions.iter().enumerate() {
19025            if idx == 0 {
19026                result.push(expr.clone());
19027                continue;
19028            }
19029            // Check if this is a struct with unnamed fields that needs name propagation
19030            if Self::struct_field_count(expr) == field_names.len()
19031                && Self::struct_has_unnamed_fields(expr)
19032            {
19033                result.push(Self::apply_struct_field_names(expr, &field_names));
19034            } else {
19035                result.push(expr.clone());
19036            }
19037        }
19038        result
19039    }
19040
19041    // Array function generators
19042
19043    fn generate_array_constructor(&mut self, f: &ArrayConstructor) -> Result<()> {
19044        // Apply struct name inheritance for target dialects that need it
19045        // (DuckDB, Spark, Databricks, Hive, Snowflake, Presto, Trino)
19046        let needs_inheritance = matches!(
19047            self.config.dialect,
19048            Some(DialectType::DuckDB)
19049                | Some(DialectType::Spark)
19050                | Some(DialectType::Databricks)
19051                | Some(DialectType::Hive)
19052                | Some(DialectType::Snowflake)
19053                | Some(DialectType::Presto)
19054                | Some(DialectType::Trino)
19055        );
19056        let propagated: Vec<Expression>;
19057        let expressions = if needs_inheritance && f.expressions.len() > 1 {
19058            propagated = Self::inherit_struct_field_names(&f.expressions);
19059            &propagated
19060        } else {
19061            &f.expressions
19062        };
19063
19064        // Check if elements should be split onto multiple lines (pretty + too wide)
19065        let should_split = if self.config.pretty && !expressions.is_empty() {
19066            let mut expr_strings: Vec<String> = Vec::with_capacity(expressions.len());
19067            for expr in expressions {
19068                let mut temp_gen = Generator::with_config(self.config.clone());
19069                temp_gen.config.pretty = false;
19070                temp_gen.generate_expression(expr)?;
19071                expr_strings.push(temp_gen.output);
19072            }
19073            self.too_wide(&expr_strings)
19074        } else {
19075            false
19076        };
19077
19078        if f.bracket_notation {
19079            // For Spark/Databricks, use ARRAY(...) with parens
19080            // For Presto/Trino/PostgreSQL, use ARRAY[...] with keyword prefix
19081            // For others (DuckDB, Snowflake), use bare [...]
19082            let (open, close) = match self.config.dialect {
19083                None
19084                | Some(DialectType::Generic)
19085                | Some(DialectType::Spark)
19086                | Some(DialectType::Databricks)
19087                | Some(DialectType::Hive) => {
19088                    self.write_keyword("ARRAY");
19089                    ("(", ")")
19090                }
19091                Some(DialectType::Presto)
19092                | Some(DialectType::Trino)
19093                | Some(DialectType::PostgreSQL)
19094                | Some(DialectType::Redshift)
19095                | Some(DialectType::Materialize)
19096                | Some(DialectType::RisingWave)
19097                | Some(DialectType::CockroachDB) => {
19098                    self.write_keyword("ARRAY");
19099                    ("[", "]")
19100                }
19101                _ => ("[", "]"),
19102            };
19103            self.write(open);
19104            if should_split {
19105                self.write_newline();
19106                self.indent_level += 1;
19107                for (i, expr) in expressions.iter().enumerate() {
19108                    self.write_indent();
19109                    self.generate_expression(expr)?;
19110                    if i + 1 < expressions.len() {
19111                        self.write(",");
19112                    }
19113                    self.write_newline();
19114                }
19115                self.indent_level -= 1;
19116                self.write_indent();
19117            } else {
19118                for (i, expr) in expressions.iter().enumerate() {
19119                    if i > 0 {
19120                        self.write(", ");
19121                    }
19122                    self.generate_expression(expr)?;
19123                }
19124            }
19125            self.write(close);
19126        } else {
19127            // Use LIST keyword if that was the original syntax (DuckDB)
19128            if f.use_list_keyword {
19129                self.write_keyword("LIST");
19130            } else {
19131                self.write_keyword("ARRAY");
19132            }
19133            // For Spark/Hive, always use ARRAY(...) with parens
19134            // Also use parens for BigQuery when the array contains a subquery (ARRAY(SELECT ...))
19135            let has_subquery = expressions
19136                .iter()
19137                .any(|e| matches!(e, Expression::Select(_)));
19138            let (open, close) = if matches!(
19139                self.config.dialect,
19140                Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive)
19141            ) || (matches!(self.config.dialect, Some(DialectType::BigQuery))
19142                && has_subquery)
19143            {
19144                ("(", ")")
19145            } else {
19146                ("[", "]")
19147            };
19148            self.write(open);
19149            if should_split {
19150                self.write_newline();
19151                self.indent_level += 1;
19152                for (i, expr) in expressions.iter().enumerate() {
19153                    self.write_indent();
19154                    self.generate_expression(expr)?;
19155                    if i + 1 < expressions.len() {
19156                        self.write(",");
19157                    }
19158                    self.write_newline();
19159                }
19160                self.indent_level -= 1;
19161                self.write_indent();
19162            } else {
19163                for (i, expr) in expressions.iter().enumerate() {
19164                    if i > 0 {
19165                        self.write(", ");
19166                    }
19167                    self.generate_expression(expr)?;
19168                }
19169            }
19170            self.write(close);
19171        }
19172        Ok(())
19173    }
19174
19175    fn generate_array_sort(&mut self, f: &ArraySortFunc) -> Result<()> {
19176        self.write_keyword("ARRAY_SORT");
19177        self.write("(");
19178        self.generate_expression(&f.this)?;
19179        if let Some(ref comp) = f.comparator {
19180            self.write(", ");
19181            self.generate_expression(comp)?;
19182        }
19183        self.write(")");
19184        Ok(())
19185    }
19186
19187    fn generate_array_join(&mut self, name: &str, f: &ArrayJoinFunc) -> Result<()> {
19188        self.write_keyword(name);
19189        self.write("(");
19190        self.generate_expression(&f.this)?;
19191        self.write(", ");
19192        self.generate_expression(&f.separator)?;
19193        if let Some(ref null_rep) = f.null_replacement {
19194            self.write(", ");
19195            self.generate_expression(null_rep)?;
19196        }
19197        self.write(")");
19198        Ok(())
19199    }
19200
19201    fn generate_unnest(&mut self, f: &UnnestFunc) -> Result<()> {
19202        self.write_keyword("UNNEST");
19203        self.write("(");
19204        self.generate_expression(&f.this)?;
19205        for extra in &f.expressions {
19206            self.write(", ");
19207            self.generate_expression(extra)?;
19208        }
19209        self.write(")");
19210        if f.with_ordinality {
19211            self.write_space();
19212            if self.config.unnest_with_ordinality {
19213                // Presto/Trino: UNNEST(arr) WITH ORDINALITY [AS alias]
19214                self.write_keyword("WITH ORDINALITY");
19215            } else if f.offset_alias.is_some() {
19216                // BigQuery: UNNEST(arr) [AS col] WITH OFFSET AS pos
19217                // Alias (if any) comes BEFORE WITH OFFSET
19218                if let Some(ref alias) = f.alias {
19219                    self.write_keyword("AS");
19220                    self.write_space();
19221                    self.generate_identifier(alias)?;
19222                    self.write_space();
19223                }
19224                self.write_keyword("WITH OFFSET");
19225                if let Some(ref offset_alias) = f.offset_alias {
19226                    self.write_space();
19227                    self.write_keyword("AS");
19228                    self.write_space();
19229                    self.generate_identifier(offset_alias)?;
19230                }
19231            } else {
19232                // WITH OFFSET (BigQuery identity) - add default "AS offset" if no explicit alias
19233                self.write_keyword("WITH OFFSET");
19234                if f.alias.is_none() {
19235                    self.write(" AS offset");
19236                }
19237            }
19238        }
19239        if let Some(ref alias) = f.alias {
19240            // Add alias for: non-WITH-OFFSET cases, Presto/Trino WITH ORDINALITY, or BigQuery WITH OFFSET + alias (no offset_alias)
19241            let should_add_alias = if !f.with_ordinality {
19242                true
19243            } else if self.config.unnest_with_ordinality {
19244                // Presto/Trino: alias comes after WITH ORDINALITY
19245                true
19246            } else if f.offset_alias.is_some() {
19247                // BigQuery expansion: alias already handled above
19248                false
19249            } else {
19250                // BigQuery WITH OFFSET + alias but no offset_alias: alias comes after
19251                true
19252            };
19253            if should_add_alias {
19254                self.write_space();
19255                self.write_keyword("AS");
19256                self.write_space();
19257                self.generate_identifier(alias)?;
19258            }
19259        }
19260        Ok(())
19261    }
19262
19263    fn generate_array_filter(&mut self, f: &ArrayFilterFunc) -> Result<()> {
19264        self.write_keyword("FILTER");
19265        self.write("(");
19266        self.generate_expression(&f.this)?;
19267        self.write(", ");
19268        self.generate_expression(&f.filter)?;
19269        self.write(")");
19270        Ok(())
19271    }
19272
19273    fn generate_array_transform(&mut self, f: &ArrayTransformFunc) -> Result<()> {
19274        self.write_keyword("TRANSFORM");
19275        self.write("(");
19276        self.generate_expression(&f.this)?;
19277        self.write(", ");
19278        self.generate_expression(&f.transform)?;
19279        self.write(")");
19280        Ok(())
19281    }
19282
19283    fn generate_sequence(&mut self, name: &str, f: &SequenceFunc) -> Result<()> {
19284        self.write_keyword(name);
19285        self.write("(");
19286        self.generate_expression(&f.start)?;
19287        self.write(", ");
19288        self.generate_expression(&f.stop)?;
19289        if let Some(ref step) = f.step {
19290            self.write(", ");
19291            self.generate_expression(step)?;
19292        }
19293        self.write(")");
19294        Ok(())
19295    }
19296
19297    // Struct function generators
19298
19299    fn generate_struct_constructor(&mut self, f: &StructConstructor) -> Result<()> {
19300        self.write_keyword("STRUCT");
19301        self.write("(");
19302        for (i, (name, expr)) in f.fields.iter().enumerate() {
19303            if i > 0 {
19304                self.write(", ");
19305            }
19306            if let Some(ref id) = name {
19307                self.generate_identifier(id)?;
19308                self.write(" ");
19309                self.write_keyword("AS");
19310                self.write(" ");
19311            }
19312            self.generate_expression(expr)?;
19313        }
19314        self.write(")");
19315        Ok(())
19316    }
19317
19318    /// Convert BigQuery STRUCT function (parsed as Function with Alias args) to target dialect
19319    fn generate_struct_function_cross_dialect(&mut self, func: &Function) -> Result<()> {
19320        // Extract named/unnamed fields from function args
19321        // Args are either Alias(this=value, alias=name) for named or plain expressions for unnamed
19322        let mut names: Vec<Option<String>> = Vec::new();
19323        let mut values: Vec<&Expression> = Vec::new();
19324        let mut all_named = true;
19325
19326        for arg in &func.args {
19327            match arg {
19328                Expression::Alias(a) => {
19329                    names.push(Some(a.alias.name.clone()));
19330                    values.push(&a.this);
19331                }
19332                _ => {
19333                    names.push(None);
19334                    values.push(arg);
19335                    all_named = false;
19336                }
19337            }
19338        }
19339
19340        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
19341            // DuckDB: {'name': value, ...} for named, {'_0': value, ...} for unnamed
19342            self.write("{");
19343            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
19344                if i > 0 {
19345                    self.write(", ");
19346                }
19347                if let Some(n) = name {
19348                    self.write("'");
19349                    self.write(n);
19350                    self.write("'");
19351                } else {
19352                    self.write("'_");
19353                    self.write(&i.to_string());
19354                    self.write("'");
19355                }
19356                self.write(": ");
19357                self.generate_expression(value)?;
19358            }
19359            self.write("}");
19360            return Ok(());
19361        }
19362
19363        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
19364            // Snowflake: OBJECT_CONSTRUCT('name', value, ...)
19365            self.write_keyword("OBJECT_CONSTRUCT");
19366            self.write("(");
19367            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
19368                if i > 0 {
19369                    self.write(", ");
19370                }
19371                if let Some(n) = name {
19372                    self.write("'");
19373                    self.write(n);
19374                    self.write("'");
19375                } else {
19376                    self.write("'_");
19377                    self.write(&i.to_string());
19378                    self.write("'");
19379                }
19380                self.write(", ");
19381                self.generate_expression(value)?;
19382            }
19383            self.write(")");
19384            return Ok(());
19385        }
19386
19387        if matches!(
19388            self.config.dialect,
19389            Some(DialectType::Presto) | Some(DialectType::Trino)
19390        ) {
19391            if all_named && !names.is_empty() {
19392                // Presto/Trino: CAST(ROW(values...) AS ROW(name TYPE, ...))
19393                // Need to infer types from values
19394                self.write_keyword("CAST");
19395                self.write("(");
19396                self.write_keyword("ROW");
19397                self.write("(");
19398                for (i, value) in values.iter().enumerate() {
19399                    if i > 0 {
19400                        self.write(", ");
19401                    }
19402                    self.generate_expression(value)?;
19403                }
19404                self.write(")");
19405                self.write(" ");
19406                self.write_keyword("AS");
19407                self.write(" ");
19408                self.write_keyword("ROW");
19409                self.write("(");
19410                for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
19411                    if i > 0 {
19412                        self.write(", ");
19413                    }
19414                    if let Some(n) = name {
19415                        self.write(n);
19416                    }
19417                    self.write(" ");
19418                    let type_str = Self::infer_sql_type_for_presto(value);
19419                    self.write_keyword(&type_str);
19420                }
19421                self.write(")");
19422                self.write(")");
19423            } else {
19424                // Unnamed: ROW(values...)
19425                self.write_keyword("ROW");
19426                self.write("(");
19427                for (i, value) in values.iter().enumerate() {
19428                    if i > 0 {
19429                        self.write(", ");
19430                    }
19431                    self.generate_expression(value)?;
19432                }
19433                self.write(")");
19434            }
19435            return Ok(());
19436        }
19437
19438        // Default: ROW(values...) for other dialects
19439        self.write_keyword("ROW");
19440        self.write("(");
19441        for (i, value) in values.iter().enumerate() {
19442            if i > 0 {
19443                self.write(", ");
19444            }
19445            self.generate_expression(value)?;
19446        }
19447        self.write(")");
19448        Ok(())
19449    }
19450
19451    /// Infer SQL type name for a Presto/Trino ROW CAST from a literal expression
19452    fn infer_sql_type_for_presto(expr: &Expression) -> String {
19453        match expr {
19454            Expression::Literal(crate::expressions::Literal::String(_)) => "VARCHAR".to_string(),
19455            Expression::Literal(crate::expressions::Literal::Number(n)) => {
19456                if n.contains('.') {
19457                    "DOUBLE".to_string()
19458                } else {
19459                    "INTEGER".to_string()
19460                }
19461            }
19462            Expression::Boolean(_) => "BOOLEAN".to_string(),
19463            Expression::Literal(crate::expressions::Literal::Date(_)) => "DATE".to_string(),
19464            Expression::Literal(crate::expressions::Literal::Timestamp(_)) => {
19465                "TIMESTAMP".to_string()
19466            }
19467            Expression::Literal(crate::expressions::Literal::Datetime(_)) => {
19468                "TIMESTAMP".to_string()
19469            }
19470            Expression::Array(_) | Expression::ArrayFunc(_) => {
19471                // Try to infer element type from first element
19472                "ARRAY(VARCHAR)".to_string()
19473            }
19474            // For nested structs - generate a nested ROW type by inspecting fields
19475            Expression::Struct(_) | Expression::StructFunc(_) => "ROW".to_string(),
19476            Expression::Function(f) => {
19477                let up = f.name.to_uppercase();
19478                if up == "STRUCT" {
19479                    "ROW".to_string()
19480                } else if up == "CURRENT_DATE" {
19481                    "DATE".to_string()
19482                } else if up == "CURRENT_TIMESTAMP" || up == "NOW" {
19483                    "TIMESTAMP".to_string()
19484                } else {
19485                    "VARCHAR".to_string()
19486                }
19487            }
19488            _ => "VARCHAR".to_string(),
19489        }
19490    }
19491
19492    fn generate_struct_extract(&mut self, f: &StructExtractFunc) -> Result<()> {
19493        // DuckDB uses STRUCT_EXTRACT function syntax
19494        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
19495            self.write_keyword("STRUCT_EXTRACT");
19496            self.write("(");
19497            self.generate_expression(&f.this)?;
19498            self.write(", ");
19499            // Output field name as string literal
19500            self.write("'");
19501            self.write(&f.field.name);
19502            self.write("'");
19503            self.write(")");
19504            return Ok(());
19505        }
19506        self.generate_expression(&f.this)?;
19507        self.write(".");
19508        self.generate_identifier(&f.field)
19509    }
19510
19511    fn generate_named_struct(&mut self, f: &NamedStructFunc) -> Result<()> {
19512        self.write_keyword("NAMED_STRUCT");
19513        self.write("(");
19514        for (i, (name, value)) in f.pairs.iter().enumerate() {
19515            if i > 0 {
19516                self.write(", ");
19517            }
19518            self.generate_expression(name)?;
19519            self.write(", ");
19520            self.generate_expression(value)?;
19521        }
19522        self.write(")");
19523        Ok(())
19524    }
19525
19526    // Map function generators
19527
19528    fn generate_map_constructor(&mut self, f: &MapConstructor) -> Result<()> {
19529        if f.curly_brace_syntax {
19530            // Curly brace syntax: MAP {'a': 1, 'b': 2} or just {'a': 1, 'b': 2}
19531            if f.with_map_keyword {
19532                self.write_keyword("MAP");
19533                self.write(" ");
19534            }
19535            self.write("{");
19536            for (i, (key, val)) in f.keys.iter().zip(f.values.iter()).enumerate() {
19537                if i > 0 {
19538                    self.write(", ");
19539                }
19540                self.generate_expression(key)?;
19541                self.write(": ");
19542                self.generate_expression(val)?;
19543            }
19544            self.write("}");
19545        } else {
19546            // MAP function syntax: MAP(ARRAY[keys], ARRAY[values])
19547            self.write_keyword("MAP");
19548            self.write("(");
19549            self.write_keyword("ARRAY");
19550            self.write("[");
19551            for (i, key) in f.keys.iter().enumerate() {
19552                if i > 0 {
19553                    self.write(", ");
19554                }
19555                self.generate_expression(key)?;
19556            }
19557            self.write("], ");
19558            self.write_keyword("ARRAY");
19559            self.write("[");
19560            for (i, val) in f.values.iter().enumerate() {
19561                if i > 0 {
19562                    self.write(", ");
19563                }
19564                self.generate_expression(val)?;
19565            }
19566            self.write("])");
19567        }
19568        Ok(())
19569    }
19570
19571    fn generate_transform_func(&mut self, name: &str, f: &TransformFunc) -> Result<()> {
19572        self.write_keyword(name);
19573        self.write("(");
19574        self.generate_expression(&f.this)?;
19575        self.write(", ");
19576        self.generate_expression(&f.transform)?;
19577        self.write(")");
19578        Ok(())
19579    }
19580
19581    // JSON function generators
19582
19583    fn generate_json_extract(&mut self, name: &str, f: &JsonExtractFunc) -> Result<()> {
19584        use crate::dialects::DialectType;
19585
19586        // Check if we should use arrow syntax (-> or ->>)
19587        let use_arrow = f.arrow_syntax && self.dialect_supports_json_arrow();
19588
19589        if use_arrow {
19590            // Output arrow syntax: expr -> path or expr ->> path
19591            self.generate_expression(&f.this)?;
19592            if name == "JSON_EXTRACT_SCALAR" || name == "JSON_EXTRACT_PATH_TEXT" {
19593                self.write(" ->> ");
19594            } else {
19595                self.write(" -> ");
19596            }
19597            self.generate_expression(&f.path)?;
19598            return Ok(());
19599        }
19600
19601        // PostgreSQL uses #>> operator for JSONB path text extraction (only when hash_arrow_syntax is true)
19602        if f.hash_arrow_syntax
19603            && matches!(
19604                self.config.dialect,
19605                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
19606            )
19607        {
19608            self.generate_expression(&f.this)?;
19609            self.write(" #>> ");
19610            self.generate_expression(&f.path)?;
19611            return Ok(());
19612        }
19613
19614        // For PostgreSQL/Redshift, use JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT for extraction without arrow syntax
19615        // Redshift maps everything to JSON_EXTRACT_PATH_TEXT since it doesn't have JSON_EXTRACT_PATH
19616        let func_name = if matches!(self.config.dialect, Some(DialectType::Redshift)) {
19617            match name {
19618                "JSON_EXTRACT_SCALAR"
19619                | "JSON_EXTRACT_PATH_TEXT"
19620                | "JSON_EXTRACT"
19621                | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH_TEXT",
19622                _ => name,
19623            }
19624        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
19625            match name {
19626                "JSON_EXTRACT_SCALAR" | "JSON_EXTRACT_PATH_TEXT" => "JSON_EXTRACT_PATH_TEXT",
19627                "JSON_EXTRACT" | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH",
19628                _ => name,
19629            }
19630        } else {
19631            name
19632        };
19633
19634        self.write_keyword(func_name);
19635        self.write("(");
19636        // For Redshift, strip CAST(... AS JSON) wrapper from the expression
19637        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
19638            if let Expression::Cast(ref cast) = f.this {
19639                if matches!(cast.to, crate::expressions::DataType::Json) {
19640                    self.generate_expression(&cast.this)?;
19641                } else {
19642                    self.generate_expression(&f.this)?;
19643                }
19644            } else {
19645                self.generate_expression(&f.this)?;
19646            }
19647        } else {
19648            self.generate_expression(&f.this)?;
19649        }
19650        // For PostgreSQL/Redshift JSON_EXTRACT_PATH/JSON_EXTRACT_PATH_TEXT,
19651        // decompose JSON path into separate string arguments
19652        if matches!(
19653            self.config.dialect,
19654            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
19655        ) && (func_name == "JSON_EXTRACT_PATH" || func_name == "JSON_EXTRACT_PATH_TEXT")
19656        {
19657            if let Expression::Literal(Literal::String(ref s)) = f.path {
19658                let parts = Self::decompose_json_path(s);
19659                for part in &parts {
19660                    self.write(", '");
19661                    self.write(part);
19662                    self.write("'");
19663                }
19664            } else {
19665                self.write(", ");
19666                self.generate_expression(&f.path)?;
19667            }
19668        } else {
19669            self.write(", ");
19670            self.generate_expression(&f.path)?;
19671        }
19672
19673        // Output JSON_QUERY/JSON_VALUE options (Trino/Presto style)
19674        // These go BEFORE the closing parenthesis
19675        if let Some(ref wrapper) = f.wrapper_option {
19676            self.write_space();
19677            self.write_keyword(wrapper);
19678        }
19679        if let Some(ref quotes) = f.quotes_option {
19680            self.write_space();
19681            self.write_keyword(quotes);
19682            if f.on_scalar_string {
19683                self.write_space();
19684                self.write_keyword("ON SCALAR STRING");
19685            }
19686        }
19687        if let Some(ref on_err) = f.on_error {
19688            self.write_space();
19689            self.write_keyword(on_err);
19690        }
19691        if let Some(ref ret_type) = f.returning {
19692            self.write_space();
19693            self.write_keyword("RETURNING");
19694            self.write_space();
19695            self.generate_data_type(ret_type)?;
19696        }
19697
19698        self.write(")");
19699        Ok(())
19700    }
19701
19702    /// Check if the current dialect supports JSON arrow operators (-> and ->>)
19703    fn dialect_supports_json_arrow(&self) -> bool {
19704        use crate::dialects::DialectType;
19705        match self.config.dialect {
19706            // PostgreSQL, MySQL, DuckDB support -> and ->> operators
19707            Some(DialectType::PostgreSQL) => true,
19708            Some(DialectType::MySQL) => true,
19709            Some(DialectType::DuckDB) => true,
19710            Some(DialectType::CockroachDB) => true,
19711            Some(DialectType::StarRocks) => true,
19712            Some(DialectType::SQLite) => true,
19713            // Other dialects use function syntax
19714            _ => false,
19715        }
19716    }
19717
19718    fn generate_json_path(&mut self, name: &str, f: &JsonPathFunc) -> Result<()> {
19719        use crate::dialects::DialectType;
19720
19721        // PostgreSQL uses #> operator for JSONB path extraction
19722        if matches!(
19723            self.config.dialect,
19724            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
19725        ) && name == "JSON_EXTRACT_PATH"
19726        {
19727            self.generate_expression(&f.this)?;
19728            self.write(" #> ");
19729            if f.paths.len() == 1 {
19730                self.generate_expression(&f.paths[0])?;
19731            } else {
19732                // Multiple paths: ARRAY[path1, path2, ...]
19733                self.write_keyword("ARRAY");
19734                self.write("[");
19735                for (i, path) in f.paths.iter().enumerate() {
19736                    if i > 0 {
19737                        self.write(", ");
19738                    }
19739                    self.generate_expression(path)?;
19740                }
19741                self.write("]");
19742            }
19743            return Ok(());
19744        }
19745
19746        self.write_keyword(name);
19747        self.write("(");
19748        self.generate_expression(&f.this)?;
19749        for path in &f.paths {
19750            self.write(", ");
19751            self.generate_expression(path)?;
19752        }
19753        self.write(")");
19754        Ok(())
19755    }
19756
19757    fn generate_json_object(&mut self, f: &JsonObjectFunc) -> Result<()> {
19758        use crate::dialects::DialectType;
19759
19760        self.write_keyword("JSON_OBJECT");
19761        self.write("(");
19762        if f.star {
19763            self.write("*");
19764        } else {
19765            // BigQuery, MySQL, and SQLite use comma syntax: JSON_OBJECT('key', value)
19766            // Standard SQL uses colon syntax: JSON_OBJECT('key': value)
19767            // Also respect the json_key_value_pair_sep config
19768            let use_comma_syntax = self.config.json_key_value_pair_sep == ","
19769                || matches!(
19770                    self.config.dialect,
19771                    Some(DialectType::BigQuery)
19772                        | Some(DialectType::MySQL)
19773                        | Some(DialectType::SQLite)
19774                );
19775
19776            for (i, (key, value)) in f.pairs.iter().enumerate() {
19777                if i > 0 {
19778                    self.write(", ");
19779                }
19780                self.generate_expression(key)?;
19781                if use_comma_syntax {
19782                    self.write(", ");
19783                } else {
19784                    self.write(": ");
19785                }
19786                self.generate_expression(value)?;
19787            }
19788        }
19789        if let Some(null_handling) = f.null_handling {
19790            self.write_space();
19791            match null_handling {
19792                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
19793                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
19794            }
19795        }
19796        if f.with_unique_keys {
19797            self.write_space();
19798            self.write_keyword("WITH UNIQUE KEYS");
19799        }
19800        if let Some(ref ret_type) = f.returning_type {
19801            self.write_space();
19802            self.write_keyword("RETURNING");
19803            self.write_space();
19804            self.generate_data_type(ret_type)?;
19805            if f.format_json {
19806                self.write_space();
19807                self.write_keyword("FORMAT JSON");
19808            }
19809            if let Some(ref enc) = f.encoding {
19810                self.write_space();
19811                self.write_keyword("ENCODING");
19812                self.write_space();
19813                self.write(enc);
19814            }
19815        }
19816        self.write(")");
19817        Ok(())
19818    }
19819
19820    fn generate_json_modify(&mut self, name: &str, f: &JsonModifyFunc) -> Result<()> {
19821        self.write_keyword(name);
19822        self.write("(");
19823        self.generate_expression(&f.this)?;
19824        for (path, value) in &f.path_values {
19825            self.write(", ");
19826            self.generate_expression(path)?;
19827            self.write(", ");
19828            self.generate_expression(value)?;
19829        }
19830        self.write(")");
19831        Ok(())
19832    }
19833
19834    fn generate_json_array_agg(&mut self, f: &JsonArrayAggFunc) -> Result<()> {
19835        self.write_keyword("JSON_ARRAYAGG");
19836        self.write("(");
19837        self.generate_expression(&f.this)?;
19838        if let Some(ref order_by) = f.order_by {
19839            self.write_space();
19840            self.write_keyword("ORDER BY");
19841            self.write_space();
19842            for (i, ord) in order_by.iter().enumerate() {
19843                if i > 0 {
19844                    self.write(", ");
19845                }
19846                self.generate_ordered(ord)?;
19847            }
19848        }
19849        if let Some(null_handling) = f.null_handling {
19850            self.write_space();
19851            match null_handling {
19852                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
19853                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
19854            }
19855        }
19856        self.write(")");
19857        if let Some(ref filter) = f.filter {
19858            self.write_space();
19859            self.write_keyword("FILTER");
19860            self.write("(");
19861            self.write_keyword("WHERE");
19862            self.write_space();
19863            self.generate_expression(filter)?;
19864            self.write(")");
19865        }
19866        Ok(())
19867    }
19868
19869    fn generate_json_object_agg(&mut self, f: &JsonObjectAggFunc) -> Result<()> {
19870        self.write_keyword("JSON_OBJECTAGG");
19871        self.write("(");
19872        self.generate_expression(&f.key)?;
19873        self.write(": ");
19874        self.generate_expression(&f.value)?;
19875        if let Some(null_handling) = f.null_handling {
19876            self.write_space();
19877            match null_handling {
19878                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
19879                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
19880            }
19881        }
19882        self.write(")");
19883        if let Some(ref filter) = f.filter {
19884            self.write_space();
19885            self.write_keyword("FILTER");
19886            self.write("(");
19887            self.write_keyword("WHERE");
19888            self.write_space();
19889            self.generate_expression(filter)?;
19890            self.write(")");
19891        }
19892        Ok(())
19893    }
19894
19895    // Type casting/conversion generators
19896
19897    fn generate_convert(&mut self, f: &ConvertFunc) -> Result<()> {
19898        use crate::dialects::DialectType;
19899
19900        // Redshift: CONVERT(type, expr) -> CAST(expr AS type)
19901        if self.config.dialect == Some(DialectType::Redshift) {
19902            self.write_keyword("CAST");
19903            self.write("(");
19904            self.generate_expression(&f.this)?;
19905            self.write_space();
19906            self.write_keyword("AS");
19907            self.write_space();
19908            self.generate_data_type(&f.to)?;
19909            self.write(")");
19910            return Ok(());
19911        }
19912
19913        self.write_keyword("CONVERT");
19914        self.write("(");
19915        self.generate_data_type(&f.to)?;
19916        self.write(", ");
19917        self.generate_expression(&f.this)?;
19918        if let Some(ref style) = f.style {
19919            self.write(", ");
19920            self.generate_expression(style)?;
19921        }
19922        self.write(")");
19923        Ok(())
19924    }
19925
19926    // Additional expression generators
19927
19928    fn generate_lambda(&mut self, f: &LambdaExpr) -> Result<()> {
19929        if f.colon {
19930            // DuckDB syntax: LAMBDA x : expr
19931            self.write_keyword("LAMBDA");
19932            self.write_space();
19933            for (i, param) in f.parameters.iter().enumerate() {
19934                if i > 0 {
19935                    self.write(", ");
19936                }
19937                self.generate_identifier(param)?;
19938            }
19939            self.write(" : ");
19940        } else {
19941            // Standard syntax: x -> expr or (x, y) -> expr
19942            if f.parameters.len() == 1 {
19943                self.generate_identifier(&f.parameters[0])?;
19944            } else {
19945                self.write("(");
19946                for (i, param) in f.parameters.iter().enumerate() {
19947                    if i > 0 {
19948                        self.write(", ");
19949                    }
19950                    self.generate_identifier(param)?;
19951                }
19952                self.write(")");
19953            }
19954            self.write(" -> ");
19955        }
19956        self.generate_expression(&f.body)
19957    }
19958
19959    fn generate_named_argument(&mut self, f: &NamedArgument) -> Result<()> {
19960        self.generate_identifier(&f.name)?;
19961        match f.separator {
19962            NamedArgSeparator::DArrow => self.write(" => "),
19963            NamedArgSeparator::ColonEq => self.write(" := "),
19964            NamedArgSeparator::Eq => self.write(" = "),
19965        }
19966        self.generate_expression(&f.value)
19967    }
19968
19969    fn generate_table_argument(&mut self, f: &TableArgument) -> Result<()> {
19970        self.write_keyword(&f.prefix);
19971        self.write(" ");
19972        self.generate_expression(&f.this)
19973    }
19974
19975    fn generate_parameter(&mut self, f: &Parameter) -> Result<()> {
19976        match f.style {
19977            ParameterStyle::Question => self.write("?"),
19978            ParameterStyle::Dollar => {
19979                self.write("$");
19980                if let Some(idx) = f.index {
19981                    self.write(&idx.to_string());
19982                } else if let Some(ref name) = f.name {
19983                    // Session variable like $x or $query_id
19984                    self.write(name);
19985                }
19986            }
19987            ParameterStyle::DollarBrace => {
19988                // Template variable like ${x} or ${hiveconf:name} (Databricks, Hive)
19989                self.write("${");
19990                if let Some(ref name) = f.name {
19991                    self.write(name);
19992                }
19993                if let Some(ref expr) = f.expression {
19994                    self.write(":");
19995                    self.write(expr);
19996                }
19997                self.write("}");
19998            }
19999            ParameterStyle::Colon => {
20000                self.write(":");
20001                if let Some(idx) = f.index {
20002                    self.write(&idx.to_string());
20003                } else if let Some(ref name) = f.name {
20004                    self.write(name);
20005                }
20006            }
20007            ParameterStyle::At => {
20008                self.write("@");
20009                if let Some(ref name) = f.name {
20010                    if f.string_quoted {
20011                        self.write("'");
20012                        self.write(name);
20013                        self.write("'");
20014                    } else if f.quoted {
20015                        self.write("\"");
20016                        self.write(name);
20017                        self.write("\"");
20018                    } else {
20019                        self.write(name);
20020                    }
20021                }
20022            }
20023            ParameterStyle::DoubleAt => {
20024                self.write("@@");
20025                if let Some(ref name) = f.name {
20026                    self.write(name);
20027                }
20028            }
20029            ParameterStyle::DoubleDollar => {
20030                self.write("$$");
20031                if let Some(ref name) = f.name {
20032                    self.write(name);
20033                }
20034            }
20035            ParameterStyle::Percent => {
20036                if let Some(ref name) = f.name {
20037                    // %(name)s format
20038                    self.write("%(");
20039                    self.write(name);
20040                    self.write(")s");
20041                } else {
20042                    // %s format
20043                    self.write("%s");
20044                }
20045            }
20046            ParameterStyle::Brace => {
20047                // Spark/Databricks widget template variable: {name}
20048                // ClickHouse query parameter may include kind: {name: Type}
20049                self.write("{");
20050                if let Some(ref name) = f.name {
20051                    self.write(name);
20052                }
20053                if let Some(ref expr) = f.expression {
20054                    self.write(": ");
20055                    self.write(expr);
20056                }
20057                self.write("}");
20058            }
20059        }
20060        Ok(())
20061    }
20062
20063    fn generate_placeholder(&mut self, f: &Placeholder) -> Result<()> {
20064        self.write("?");
20065        if let Some(idx) = f.index {
20066            self.write(&idx.to_string());
20067        }
20068        Ok(())
20069    }
20070
20071    fn generate_sql_comment(&mut self, f: &SqlComment) -> Result<()> {
20072        if f.is_block {
20073            self.write("/*");
20074            self.write(&f.text);
20075            self.write("*/");
20076        } else {
20077            self.write("--");
20078            self.write(&f.text);
20079        }
20080        Ok(())
20081    }
20082
20083    // Additional predicate generators
20084
20085    fn generate_similar_to(&mut self, f: &SimilarToExpr) -> Result<()> {
20086        self.generate_expression(&f.this)?;
20087        if f.not {
20088            self.write_space();
20089            self.write_keyword("NOT");
20090        }
20091        self.write_space();
20092        self.write_keyword("SIMILAR TO");
20093        self.write_space();
20094        self.generate_expression(&f.pattern)?;
20095        if let Some(ref escape) = f.escape {
20096            self.write_space();
20097            self.write_keyword("ESCAPE");
20098            self.write_space();
20099            self.generate_expression(escape)?;
20100        }
20101        Ok(())
20102    }
20103
20104    fn generate_quantified(&mut self, name: &str, f: &QuantifiedExpr) -> Result<()> {
20105        self.generate_expression(&f.this)?;
20106        self.write_space();
20107        // Output comparison operator if present
20108        if let Some(op) = &f.op {
20109            match op {
20110                QuantifiedOp::Eq => self.write("="),
20111                QuantifiedOp::Neq => self.write("<>"),
20112                QuantifiedOp::Lt => self.write("<"),
20113                QuantifiedOp::Lte => self.write("<="),
20114                QuantifiedOp::Gt => self.write(">"),
20115                QuantifiedOp::Gte => self.write(">="),
20116            }
20117            self.write_space();
20118        }
20119        self.write_keyword(name);
20120
20121        // If the child is a Subquery, it provides its own parens — output with space
20122        if matches!(&f.subquery, Expression::Subquery(_)) {
20123            self.write_space();
20124            self.generate_expression(&f.subquery)?;
20125        } else {
20126            self.write("(");
20127
20128            let is_statement = matches!(
20129                &f.subquery,
20130                Expression::Select(_)
20131                    | Expression::Union(_)
20132                    | Expression::Intersect(_)
20133                    | Expression::Except(_)
20134            );
20135
20136            if self.config.pretty && is_statement {
20137                self.write_newline();
20138                self.indent_level += 1;
20139                self.write_indent();
20140            }
20141            self.generate_expression(&f.subquery)?;
20142            if self.config.pretty && is_statement {
20143                self.write_newline();
20144                self.indent_level -= 1;
20145                self.write_indent();
20146            }
20147            self.write(")");
20148        }
20149        Ok(())
20150    }
20151
20152    fn generate_overlaps(&mut self, f: &OverlapsExpr) -> Result<()> {
20153        // Check if this is a simple binary form (this OVERLAPS expression)
20154        if let (Some(this), Some(expr)) = (&f.this, &f.expression) {
20155            self.generate_expression(this)?;
20156            self.write_space();
20157            self.write_keyword("OVERLAPS");
20158            self.write_space();
20159            self.generate_expression(expr)?;
20160        } else if let (Some(ls), Some(le), Some(rs), Some(re)) =
20161            (&f.left_start, &f.left_end, &f.right_start, &f.right_end)
20162        {
20163            // Full ANSI form: (a, b) OVERLAPS (c, d)
20164            self.write("(");
20165            self.generate_expression(ls)?;
20166            self.write(", ");
20167            self.generate_expression(le)?;
20168            self.write(")");
20169            self.write_space();
20170            self.write_keyword("OVERLAPS");
20171            self.write_space();
20172            self.write("(");
20173            self.generate_expression(rs)?;
20174            self.write(", ");
20175            self.generate_expression(re)?;
20176            self.write(")");
20177        }
20178        Ok(())
20179    }
20180
20181    // Type conversion generators
20182
20183    fn generate_try_cast(&mut self, cast: &Cast) -> Result<()> {
20184        use crate::dialects::DialectType;
20185
20186        // SingleStore uses !:> syntax for try cast
20187        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
20188            self.generate_expression(&cast.this)?;
20189            self.write(" !:> ");
20190            self.generate_data_type(&cast.to)?;
20191            return Ok(());
20192        }
20193
20194        // Teradata uses TRYCAST (no underscore)
20195        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
20196            self.write_keyword("TRYCAST");
20197            self.write("(");
20198            self.generate_expression(&cast.this)?;
20199            self.write_space();
20200            self.write_keyword("AS");
20201            self.write_space();
20202            self.generate_data_type(&cast.to)?;
20203            self.write(")");
20204            return Ok(());
20205        }
20206
20207        // Dialects without TRY_CAST: generate as regular CAST
20208        let keyword = if matches!(
20209            self.config.dialect,
20210            Some(DialectType::Hive)
20211                | Some(DialectType::MySQL)
20212                | Some(DialectType::SQLite)
20213                | Some(DialectType::Oracle)
20214                | Some(DialectType::ClickHouse)
20215                | Some(DialectType::Redshift)
20216                | Some(DialectType::PostgreSQL)
20217                | Some(DialectType::StarRocks)
20218                | Some(DialectType::Doris)
20219        ) {
20220            "CAST"
20221        } else {
20222            "TRY_CAST"
20223        };
20224
20225        self.write_keyword(keyword);
20226        self.write("(");
20227        self.generate_expression(&cast.this)?;
20228        self.write_space();
20229        self.write_keyword("AS");
20230        self.write_space();
20231        self.generate_data_type(&cast.to)?;
20232
20233        // Output FORMAT clause if present
20234        if let Some(format) = &cast.format {
20235            self.write_space();
20236            self.write_keyword("FORMAT");
20237            self.write_space();
20238            self.generate_expression(format)?;
20239        }
20240
20241        self.write(")");
20242        Ok(())
20243    }
20244
20245    fn generate_safe_cast(&mut self, cast: &Cast) -> Result<()> {
20246        self.write_keyword("SAFE_CAST");
20247        self.write("(");
20248        self.generate_expression(&cast.this)?;
20249        self.write_space();
20250        self.write_keyword("AS");
20251        self.write_space();
20252        self.generate_data_type(&cast.to)?;
20253
20254        // Output FORMAT clause if present
20255        if let Some(format) = &cast.format {
20256            self.write_space();
20257            self.write_keyword("FORMAT");
20258            self.write_space();
20259            self.generate_expression(format)?;
20260        }
20261
20262        self.write(")");
20263        Ok(())
20264    }
20265
20266    // Array/struct/map access generators
20267
20268    fn generate_subscript(&mut self, s: &Subscript) -> Result<()> {
20269        self.generate_expression(&s.this)?;
20270        self.write("[");
20271        self.generate_expression(&s.index)?;
20272        self.write("]");
20273        Ok(())
20274    }
20275
20276    fn generate_dot_access(&mut self, d: &DotAccess) -> Result<()> {
20277        self.generate_expression(&d.this)?;
20278        // Snowflake uses : (colon) for first-level struct/object field access on CAST/column expressions
20279        // e.g., CAST(col AS OBJECT(fld1 OBJECT(fld2 INT))):fld1.fld2
20280        let use_colon = matches!(self.config.dialect, Some(DialectType::Snowflake))
20281            && matches!(
20282                &d.this,
20283                Expression::Cast(_) | Expression::SafeCast(_) | Expression::TryCast(_)
20284            );
20285        if use_colon {
20286            self.write(":");
20287        } else {
20288            self.write(".");
20289        }
20290        self.generate_identifier(&d.field)
20291    }
20292
20293    fn generate_method_call(&mut self, m: &MethodCall) -> Result<()> {
20294        self.generate_expression(&m.this)?;
20295        self.write(".");
20296        // Method names after a dot should not be quoted based on reserved keywords
20297        // Only quote if explicitly marked as quoted in the AST
20298        if m.method.quoted {
20299            let q = self.config.identifier_quote;
20300            self.write(&format!("{}{}{}", q, m.method.name, q));
20301        } else {
20302            self.write(&m.method.name);
20303        }
20304        self.write("(");
20305        for (i, arg) in m.args.iter().enumerate() {
20306            if i > 0 {
20307                self.write(", ");
20308            }
20309            self.generate_expression(arg)?;
20310        }
20311        self.write(")");
20312        Ok(())
20313    }
20314
20315    fn generate_array_slice(&mut self, s: &ArraySlice) -> Result<()> {
20316        // Check if we need to wrap the inner expression in parentheses
20317        // JSON arrow expressions have lower precedence than array subscript
20318        let needs_parens = matches!(
20319            &s.this,
20320            Expression::JsonExtract(f) if f.arrow_syntax
20321        ) || matches!(
20322            &s.this,
20323            Expression::JsonExtractScalar(f) if f.arrow_syntax
20324        );
20325
20326        if needs_parens {
20327            self.write("(");
20328        }
20329        self.generate_expression(&s.this)?;
20330        if needs_parens {
20331            self.write(")");
20332        }
20333        self.write("[");
20334        if let Some(start) = &s.start {
20335            self.generate_expression(start)?;
20336        }
20337        self.write(":");
20338        if let Some(end) = &s.end {
20339            self.generate_expression(end)?;
20340        }
20341        self.write("]");
20342        Ok(())
20343    }
20344
20345    fn generate_binary_op(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
20346        // Generate left expression, but skip trailing comments if they're already in left_comments
20347        // to avoid duplication (comments are captured as both expr.trailing_comments
20348        // and BinaryOp.left_comments during parsing)
20349        match &op.left {
20350            Expression::Column(col) => {
20351                // Generate column with trailing comments but skip them if they're
20352                // already captured in BinaryOp.left_comments to avoid duplication
20353                if let Some(table) = &col.table {
20354                    self.generate_identifier(table)?;
20355                    self.write(".");
20356                }
20357                self.generate_identifier(&col.name)?;
20358                // Oracle-style join marker (+)
20359                if col.join_mark && self.config.supports_column_join_marks {
20360                    self.write(" (+)");
20361                }
20362                // Output column trailing comments if they're not already in left_comments
20363                if op.left_comments.is_empty() {
20364                    for comment in &col.trailing_comments {
20365                        self.write_space();
20366                        self.write_formatted_comment(comment);
20367                    }
20368                }
20369            }
20370            Expression::Add(inner_op)
20371            | Expression::Sub(inner_op)
20372            | Expression::Mul(inner_op)
20373            | Expression::Div(inner_op)
20374            | Expression::Concat(inner_op) => {
20375                // Generate binary op without its trailing comments
20376                self.generate_binary_op_no_trailing(inner_op, match &op.left {
20377                    Expression::Add(_) => "+",
20378                    Expression::Sub(_) => "-",
20379                    Expression::Mul(_) => "*",
20380                    Expression::Div(_) => "/",
20381                    Expression::Concat(_) => "||",
20382                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
20383                })?;
20384            }
20385            _ => {
20386                self.generate_expression(&op.left)?;
20387            }
20388        }
20389        // Output comments after left operand
20390        for comment in &op.left_comments {
20391            self.write_space();
20392            self.write_formatted_comment(comment);
20393        }
20394        if self.config.pretty
20395            && matches!(self.config.dialect, Some(DialectType::Snowflake))
20396            && (operator == "AND" || operator == "OR")
20397        {
20398            self.write_newline();
20399            self.write_indent();
20400            self.write_keyword(operator);
20401        } else {
20402            self.write_space();
20403            if operator.chars().all(|c| c.is_alphabetic()) {
20404                self.write_keyword(operator);
20405            } else {
20406                self.write(operator);
20407            }
20408        }
20409        // Output comments after operator (before right operand)
20410        for comment in &op.operator_comments {
20411            self.write_space();
20412            self.write_formatted_comment(comment);
20413        }
20414        self.write_space();
20415        self.generate_expression(&op.right)?;
20416        // Output trailing comments after right operand
20417        for comment in &op.trailing_comments {
20418            self.write_space();
20419            self.write_formatted_comment(comment);
20420        }
20421        Ok(())
20422    }
20423
20424    /// Generate LIKE/ILIKE operation with optional ESCAPE clause
20425    fn generate_like_op(&mut self, op: &LikeOp, operator: &str) -> Result<()> {
20426        self.generate_expression(&op.left)?;
20427        self.write_space();
20428        // Drill backtick-quotes ILIKE
20429        if operator == "ILIKE" && matches!(self.config.dialect, Some(DialectType::Drill)) {
20430            self.write("`ILIKE`");
20431        } else {
20432            self.write_keyword(operator);
20433        }
20434        if let Some(quantifier) = &op.quantifier {
20435            self.write_space();
20436            self.write_keyword(quantifier);
20437        }
20438        self.write_space();
20439        self.generate_expression(&op.right)?;
20440        if let Some(escape) = &op.escape {
20441            self.write_space();
20442            self.write_keyword("ESCAPE");
20443            self.write_space();
20444            self.generate_expression(escape)?;
20445        }
20446        Ok(())
20447    }
20448
20449    /// Generate null-safe equality
20450    /// MySQL uses <=>, other dialects use IS NOT DISTINCT FROM
20451    fn generate_null_safe_eq(&mut self, op: &BinaryOp) -> Result<()> {
20452        use crate::dialects::DialectType;
20453        self.generate_expression(&op.left)?;
20454        self.write_space();
20455        if matches!(self.config.dialect, Some(DialectType::MySQL)) {
20456            self.write("<=>");
20457        } else {
20458            self.write_keyword("IS NOT DISTINCT FROM");
20459        }
20460        self.write_space();
20461        self.generate_expression(&op.right)?;
20462        Ok(())
20463    }
20464
20465    /// Generate IS DISTINCT FROM (null-safe inequality)
20466    fn generate_null_safe_neq(&mut self, op: &BinaryOp) -> Result<()> {
20467        self.generate_expression(&op.left)?;
20468        self.write_space();
20469        self.write_keyword("IS DISTINCT FROM");
20470        self.write_space();
20471        self.generate_expression(&op.right)?;
20472        Ok(())
20473    }
20474
20475    /// Generate binary op without trailing comments (used when nested inside another binary op)
20476    fn generate_binary_op_no_trailing(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
20477        // Generate left expression, but skip trailing comments
20478        match &op.left {
20479            Expression::Column(col) => {
20480                if let Some(table) = &col.table {
20481                    self.generate_identifier(table)?;
20482                    self.write(".");
20483                }
20484                self.generate_identifier(&col.name)?;
20485                // Oracle-style join marker (+)
20486                if col.join_mark && self.config.supports_column_join_marks {
20487                    self.write(" (+)");
20488                }
20489            }
20490            Expression::Add(inner_op)
20491            | Expression::Sub(inner_op)
20492            | Expression::Mul(inner_op)
20493            | Expression::Div(inner_op)
20494            | Expression::Concat(inner_op) => {
20495                self.generate_binary_op_no_trailing(inner_op, match &op.left {
20496                    Expression::Add(_) => "+",
20497                    Expression::Sub(_) => "-",
20498                    Expression::Mul(_) => "*",
20499                    Expression::Div(_) => "/",
20500                    Expression::Concat(_) => "||",
20501                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
20502                })?;
20503            }
20504            _ => {
20505                self.generate_expression(&op.left)?;
20506            }
20507        }
20508        // Output left_comments
20509        for comment in &op.left_comments {
20510            self.write_space();
20511            self.write_formatted_comment(comment);
20512        }
20513        self.write_space();
20514        if operator.chars().all(|c| c.is_alphabetic()) {
20515            self.write_keyword(operator);
20516        } else {
20517            self.write(operator);
20518        }
20519        // Output operator_comments
20520        for comment in &op.operator_comments {
20521            self.write_space();
20522            self.write_formatted_comment(comment);
20523        }
20524        self.write_space();
20525        // Generate right expression, but skip trailing comments if it's a Column
20526        // (the parent's left_comments will output them)
20527        match &op.right {
20528            Expression::Column(col) => {
20529                if let Some(table) = &col.table {
20530                    self.generate_identifier(table)?;
20531                    self.write(".");
20532                }
20533                self.generate_identifier(&col.name)?;
20534                // Oracle-style join marker (+)
20535                if col.join_mark && self.config.supports_column_join_marks {
20536                    self.write(" (+)");
20537                }
20538            }
20539            _ => {
20540                self.generate_expression(&op.right)?;
20541            }
20542        }
20543        // Skip trailing_comments - parent will handle them via its left_comments
20544        Ok(())
20545    }
20546
20547    fn generate_unary_op(&mut self, op: &UnaryOp, operator: &str) -> Result<()> {
20548        if operator.chars().all(|c| c.is_alphabetic()) {
20549            self.write_keyword(operator);
20550            self.write_space();
20551        } else {
20552            self.write(operator);
20553            // Add space between consecutive unary operators (e.g., "- -5" not "--5")
20554            if matches!(&op.this, Expression::Neg(_) | Expression::BitwiseNot(_)) {
20555                self.write_space();
20556            }
20557        }
20558        self.generate_expression(&op.this)
20559    }
20560
20561    fn generate_in(&mut self, in_expr: &In) -> Result<()> {
20562        // Generic mode supports two styles for negated IN:
20563        // - Prefix: NOT a IN (...)
20564        // - Infix:  a NOT IN (...)
20565        let is_generic =
20566            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
20567        let use_prefix_not =
20568            in_expr.not && is_generic && self.config.not_in_style == NotInStyle::Prefix;
20569        if use_prefix_not {
20570            self.write_keyword("NOT");
20571            self.write_space();
20572        }
20573        self.generate_expression(&in_expr.this)?;
20574        if in_expr.global {
20575            self.write_space();
20576            self.write_keyword("GLOBAL");
20577        }
20578        if in_expr.not && !use_prefix_not {
20579            self.write_space();
20580            self.write_keyword("NOT");
20581        }
20582        self.write_space();
20583        self.write_keyword("IN");
20584
20585        // BigQuery: IN UNNEST(expr)
20586        if let Some(unnest_expr) = &in_expr.unnest {
20587            self.write_space();
20588            self.write_keyword("UNNEST");
20589            self.write("(");
20590            self.generate_expression(unnest_expr)?;
20591            self.write(")");
20592            return Ok(());
20593        }
20594
20595        if let Some(query) = &in_expr.query {
20596            // Check if this is a bare identifier (PIVOT FOR foo IN y_enum)
20597            // vs a subquery (col IN (SELECT ...))
20598            let is_bare = in_expr.expressions.is_empty()
20599                && !matches!(
20600                    query,
20601                    Expression::Select(_)
20602                        | Expression::Union(_)
20603                        | Expression::Intersect(_)
20604                        | Expression::Except(_)
20605                        | Expression::Subquery(_)
20606                );
20607            if is_bare {
20608                // Bare identifier: no parentheses
20609                self.write_space();
20610                self.generate_expression(query)?;
20611            } else {
20612                // Subquery: with parentheses
20613                self.write(" (");
20614                let is_statement = matches!(
20615                    query,
20616                    Expression::Select(_)
20617                        | Expression::Union(_)
20618                        | Expression::Intersect(_)
20619                        | Expression::Except(_)
20620                        | Expression::Subquery(_)
20621                );
20622                if self.config.pretty && is_statement {
20623                    self.write_newline();
20624                    self.indent_level += 1;
20625                    self.write_indent();
20626                }
20627                self.generate_expression(query)?;
20628                if self.config.pretty && is_statement {
20629                    self.write_newline();
20630                    self.indent_level -= 1;
20631                    self.write_indent();
20632                }
20633                self.write(")");
20634            }
20635        } else {
20636            // DuckDB: IN without parentheses for single expression that is NOT a literal
20637            // (array/list membership like 'red' IN tbl.flags)
20638            // ClickHouse: IN without parentheses for single non-array expressions
20639            let is_duckdb = matches!(
20640                self.config.dialect,
20641                Some(crate::dialects::DialectType::DuckDB)
20642            );
20643            let is_clickhouse = matches!(
20644                self.config.dialect,
20645                Some(crate::dialects::DialectType::ClickHouse)
20646            );
20647            let single_expr = in_expr.expressions.len() == 1;
20648            if is_clickhouse && single_expr {
20649                if let Expression::Array(arr) = &in_expr.expressions[0] {
20650                    // ClickHouse: x IN [1, 2] -> x IN (1, 2)
20651                    self.write(" (");
20652                    for (i, expr) in arr.expressions.iter().enumerate() {
20653                        if i > 0 {
20654                            self.write(", ");
20655                        }
20656                        self.generate_expression(expr)?;
20657                    }
20658                    self.write(")");
20659                } else {
20660                    self.write_space();
20661                    self.generate_expression(&in_expr.expressions[0])?;
20662                }
20663            } else {
20664                let is_bare_ref = single_expr
20665                    && matches!(
20666                        &in_expr.expressions[0],
20667                        Expression::Column(_) | Expression::Identifier(_) | Expression::Dot(_)
20668                    );
20669                if (is_duckdb && is_bare_ref) || (in_expr.is_field && single_expr) {
20670                    // Bare field reference (no parens in source): IN identifier
20671                    // Also DuckDB: IN without parentheses for array/list membership
20672                    self.write_space();
20673                    self.generate_expression(&in_expr.expressions[0])?;
20674                } else {
20675                    // Standard IN (list)
20676                    self.write(" (");
20677                    for (i, expr) in in_expr.expressions.iter().enumerate() {
20678                        if i > 0 {
20679                            self.write(", ");
20680                        }
20681                        self.generate_expression(expr)?;
20682                    }
20683                    self.write(")");
20684                }
20685            }
20686        }
20687
20688        Ok(())
20689    }
20690
20691    fn generate_between(&mut self, between: &Between) -> Result<()> {
20692        // Generic mode: normalize NOT BETWEEN to prefix form: NOT a BETWEEN b AND c
20693        let use_prefix_not = between.not
20694            && (self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic));
20695        if use_prefix_not {
20696            self.write_keyword("NOT");
20697            self.write_space();
20698        }
20699        self.generate_expression(&between.this)?;
20700        if between.not && !use_prefix_not {
20701            self.write_space();
20702            self.write_keyword("NOT");
20703        }
20704        self.write_space();
20705        self.write_keyword("BETWEEN");
20706        // Emit SYMMETRIC/ASYMMETRIC if present
20707        if let Some(sym) = between.symmetric {
20708            if sym {
20709                self.write(" SYMMETRIC");
20710            } else {
20711                self.write(" ASYMMETRIC");
20712            }
20713        }
20714        self.write_space();
20715        self.generate_expression(&between.low)?;
20716        self.write_space();
20717        self.write_keyword("AND");
20718        self.write_space();
20719        self.generate_expression(&between.high)
20720    }
20721
20722    fn generate_is_null(&mut self, is_null: &IsNull) -> Result<()> {
20723        // Generic mode: normalize IS NOT NULL to prefix form: NOT x IS NULL
20724        let use_prefix_not = is_null.not
20725            && (self.config.dialect.is_none()
20726                || self.config.dialect == Some(DialectType::Generic)
20727                || is_null.postfix_form);
20728        if use_prefix_not {
20729            // NOT x IS NULL (generic normalization and NOTNULL postfix form)
20730            self.write_keyword("NOT");
20731            self.write_space();
20732            self.generate_expression(&is_null.this)?;
20733            self.write_space();
20734            self.write_keyword("IS");
20735            self.write_space();
20736            self.write_keyword("NULL");
20737        } else {
20738            self.generate_expression(&is_null.this)?;
20739            self.write_space();
20740            self.write_keyword("IS");
20741            if is_null.not {
20742                self.write_space();
20743                self.write_keyword("NOT");
20744            }
20745            self.write_space();
20746            self.write_keyword("NULL");
20747        }
20748        Ok(())
20749    }
20750
20751    fn generate_is_true(&mut self, is_true: &IsTrueFalse) -> Result<()> {
20752        self.generate_expression(&is_true.this)?;
20753        self.write_space();
20754        self.write_keyword("IS");
20755        if is_true.not {
20756            self.write_space();
20757            self.write_keyword("NOT");
20758        }
20759        self.write_space();
20760        self.write_keyword("TRUE");
20761        Ok(())
20762    }
20763
20764    fn generate_is_false(&mut self, is_false: &IsTrueFalse) -> Result<()> {
20765        self.generate_expression(&is_false.this)?;
20766        self.write_space();
20767        self.write_keyword("IS");
20768        if is_false.not {
20769            self.write_space();
20770            self.write_keyword("NOT");
20771        }
20772        self.write_space();
20773        self.write_keyword("FALSE");
20774        Ok(())
20775    }
20776
20777    fn generate_is_json(&mut self, is_json: &IsJson) -> Result<()> {
20778        self.generate_expression(&is_json.this)?;
20779        self.write_space();
20780        self.write_keyword("IS");
20781        if is_json.negated {
20782            self.write_space();
20783            self.write_keyword("NOT");
20784        }
20785        self.write_space();
20786        self.write_keyword("JSON");
20787
20788        // Output JSON type if specified (VALUE, SCALAR, OBJECT, ARRAY)
20789        if let Some(ref json_type) = is_json.json_type {
20790            self.write_space();
20791            self.write_keyword(json_type);
20792        }
20793
20794        // Output key uniqueness constraint if specified
20795        match &is_json.unique_keys {
20796            Some(JsonUniqueKeys::With) => {
20797                self.write_space();
20798                self.write_keyword("WITH UNIQUE KEYS");
20799            }
20800            Some(JsonUniqueKeys::Without) => {
20801                self.write_space();
20802                self.write_keyword("WITHOUT UNIQUE KEYS");
20803            }
20804            Some(JsonUniqueKeys::Shorthand) => {
20805                self.write_space();
20806                self.write_keyword("UNIQUE KEYS");
20807            }
20808            None => {}
20809        }
20810
20811        Ok(())
20812    }
20813
20814    fn generate_is(&mut self, is_expr: &BinaryOp) -> Result<()> {
20815        self.generate_expression(&is_expr.left)?;
20816        self.write_space();
20817        self.write_keyword("IS");
20818        self.write_space();
20819        self.generate_expression(&is_expr.right)
20820    }
20821
20822    fn generate_exists(&mut self, exists: &Exists) -> Result<()> {
20823        if exists.not {
20824            self.write_keyword("NOT");
20825            self.write_space();
20826        }
20827        self.write_keyword("EXISTS");
20828        self.write("(");
20829        let is_statement = matches!(
20830            &exists.this,
20831            Expression::Select(_)
20832                | Expression::Union(_)
20833                | Expression::Intersect(_)
20834                | Expression::Except(_)
20835        );
20836        if self.config.pretty && is_statement {
20837            self.write_newline();
20838            self.indent_level += 1;
20839            self.write_indent();
20840            self.generate_expression(&exists.this)?;
20841            self.write_newline();
20842            self.indent_level -= 1;
20843            self.write_indent();
20844            self.write(")");
20845        } else {
20846            self.generate_expression(&exists.this)?;
20847            self.write(")");
20848        }
20849        Ok(())
20850    }
20851
20852    fn generate_member_of(&mut self, op: &BinaryOp) -> Result<()> {
20853        self.generate_expression(&op.left)?;
20854        self.write_space();
20855        self.write_keyword("MEMBER OF");
20856        self.write("(");
20857        self.generate_expression(&op.right)?;
20858        self.write(")");
20859        Ok(())
20860    }
20861
20862    fn generate_subquery(&mut self, subquery: &Subquery) -> Result<()> {
20863        if subquery.lateral {
20864            self.write_keyword("LATERAL");
20865            self.write_space();
20866        }
20867
20868        // If the inner expression is a Paren wrapping a statement, don't add extra parentheses
20869        // This handles cases like ((SELECT 1)) LIMIT 1 where we wrap Paren in Subquery
20870        // to carry the LIMIT modifier without adding more parens
20871        let skip_outer_parens = if let Expression::Paren(ref p) = &subquery.this {
20872            matches!(
20873                &p.this,
20874                Expression::Select(_)
20875                    | Expression::Union(_)
20876                    | Expression::Intersect(_)
20877                    | Expression::Except(_)
20878                    | Expression::Subquery(_)
20879            )
20880        } else {
20881            false
20882        };
20883
20884        // Check if inner expression is a statement for pretty formatting
20885        let is_statement = matches!(
20886            &subquery.this,
20887            Expression::Select(_)
20888                | Expression::Union(_)
20889                | Expression::Intersect(_)
20890                | Expression::Except(_)
20891                | Expression::Merge(_)
20892        );
20893
20894        if !skip_outer_parens {
20895            self.write("(");
20896            if self.config.pretty && is_statement {
20897                self.write_newline();
20898                self.indent_level += 1;
20899                self.write_indent();
20900            }
20901        }
20902        self.generate_expression(&subquery.this)?;
20903
20904        // Generate ORDER BY, LIMIT, OFFSET based on modifiers_inside flag
20905        if subquery.modifiers_inside {
20906            // Generate modifiers INSIDE the parentheses: (SELECT ... LIMIT 1)
20907            if let Some(order_by) = &subquery.order_by {
20908                self.write_space();
20909                self.write_keyword("ORDER BY");
20910                self.write_space();
20911                for (i, ord) in order_by.expressions.iter().enumerate() {
20912                    if i > 0 {
20913                        self.write(", ");
20914                    }
20915                    self.generate_ordered(ord)?;
20916                }
20917            }
20918
20919            if let Some(limit) = &subquery.limit {
20920                self.write_space();
20921                self.write_keyword("LIMIT");
20922                self.write_space();
20923                self.generate_expression(&limit.this)?;
20924                if limit.percent {
20925                    self.write_space();
20926                    self.write_keyword("PERCENT");
20927                }
20928            }
20929
20930            if let Some(offset) = &subquery.offset {
20931                self.write_space();
20932                self.write_keyword("OFFSET");
20933                self.write_space();
20934                self.generate_expression(&offset.this)?;
20935            }
20936        }
20937
20938        if !skip_outer_parens {
20939            if self.config.pretty && is_statement {
20940                self.write_newline();
20941                self.indent_level -= 1;
20942                self.write_indent();
20943            }
20944            self.write(")");
20945        }
20946
20947        // Generate modifiers OUTSIDE the parentheses: (SELECT ...) LIMIT 1
20948        if !subquery.modifiers_inside {
20949            if let Some(order_by) = &subquery.order_by {
20950                self.write_space();
20951                self.write_keyword("ORDER BY");
20952                self.write_space();
20953                for (i, ord) in order_by.expressions.iter().enumerate() {
20954                    if i > 0 {
20955                        self.write(", ");
20956                    }
20957                    self.generate_ordered(ord)?;
20958                }
20959            }
20960
20961            if let Some(limit) = &subquery.limit {
20962                self.write_space();
20963                self.write_keyword("LIMIT");
20964                self.write_space();
20965                self.generate_expression(&limit.this)?;
20966                if limit.percent {
20967                    self.write_space();
20968                    self.write_keyword("PERCENT");
20969                }
20970            }
20971
20972            if let Some(offset) = &subquery.offset {
20973                self.write_space();
20974                self.write_keyword("OFFSET");
20975                self.write_space();
20976                self.generate_expression(&offset.this)?;
20977            }
20978
20979            // Generate DISTRIBUTE BY (Hive/Spark)
20980            if let Some(distribute_by) = &subquery.distribute_by {
20981                self.write_space();
20982                self.write_keyword("DISTRIBUTE BY");
20983                self.write_space();
20984                for (i, expr) in distribute_by.expressions.iter().enumerate() {
20985                    if i > 0 {
20986                        self.write(", ");
20987                    }
20988                    self.generate_expression(expr)?;
20989                }
20990            }
20991
20992            // Generate SORT BY (Hive/Spark)
20993            if let Some(sort_by) = &subquery.sort_by {
20994                self.write_space();
20995                self.write_keyword("SORT BY");
20996                self.write_space();
20997                for (i, ord) in sort_by.expressions.iter().enumerate() {
20998                    if i > 0 {
20999                        self.write(", ");
21000                    }
21001                    self.generate_ordered(ord)?;
21002                }
21003            }
21004
21005            // Generate CLUSTER BY (Hive/Spark)
21006            if let Some(cluster_by) = &subquery.cluster_by {
21007                self.write_space();
21008                self.write_keyword("CLUSTER BY");
21009                self.write_space();
21010                for (i, ord) in cluster_by.expressions.iter().enumerate() {
21011                    if i > 0 {
21012                        self.write(", ");
21013                    }
21014                    self.generate_ordered(ord)?;
21015                }
21016            }
21017        }
21018
21019        if let Some(alias) = &subquery.alias {
21020            self.write_space();
21021            // Oracle doesn't use AS for subquery aliases
21022            let skip_as = matches!(
21023                self.config.dialect,
21024                Some(crate::dialects::DialectType::Oracle)
21025            );
21026            if !skip_as {
21027                self.write_keyword("AS");
21028                self.write_space();
21029            }
21030            self.generate_identifier(alias)?;
21031            if !subquery.column_aliases.is_empty() {
21032                self.write("(");
21033                for (i, col) in subquery.column_aliases.iter().enumerate() {
21034                    if i > 0 {
21035                        self.write(", ");
21036                    }
21037                    self.generate_identifier(col)?;
21038                }
21039                self.write(")");
21040            }
21041        }
21042        // Output trailing comments
21043        for comment in &subquery.trailing_comments {
21044            self.write(" ");
21045            self.write_formatted_comment(comment);
21046        }
21047        Ok(())
21048    }
21049
21050    fn generate_pivot(&mut self, pivot: &Pivot) -> Result<()> {
21051        // Generate WITH clause if present
21052        if let Some(ref with) = pivot.with {
21053            self.generate_with(with)?;
21054            self.write_space();
21055        }
21056
21057        let direction = if pivot.unpivot { "UNPIVOT" } else { "PIVOT" };
21058
21059        // Check for Redshift UNPIVOT in FROM clause:
21060        // UNPIVOT expr [AS val AT attr]
21061        // This is when unpivot=true, expressions is empty, fields is empty, and this is not Null
21062        let is_redshift_unpivot = pivot.unpivot
21063            && pivot.expressions.is_empty()
21064            && pivot.fields.is_empty()
21065            && pivot.using.is_empty()
21066            && pivot.into.is_none()
21067            && !matches!(&pivot.this, Expression::Null(_));
21068
21069        if is_redshift_unpivot {
21070            // Redshift UNPIVOT: UNPIVOT expr [AS alias]
21071            self.write_keyword("UNPIVOT");
21072            self.write_space();
21073            self.generate_expression(&pivot.this)?;
21074            // Alias - for Redshift it can be "val AT attr" format
21075            if let Some(alias) = &pivot.alias {
21076                self.write_space();
21077                self.write_keyword("AS");
21078                self.write_space();
21079                // The alias might contain " AT " for the attr part
21080                self.write(&alias.name);
21081            }
21082            return Ok(());
21083        }
21084
21085        // Check if this is a DuckDB simplified pivot (has `using` or `into`, or no `fields`)
21086        let is_simplified = !pivot.using.is_empty()
21087            || pivot.into.is_some()
21088            || (pivot.fields.is_empty()
21089                && !pivot.expressions.is_empty()
21090                && !matches!(&pivot.this, Expression::Null(_)));
21091
21092        if is_simplified {
21093            // DuckDB simplified syntax:
21094            //   PIVOT table ON cols [IN (...)] USING agg [AS alias], ... [GROUP BY ...]
21095            //   UNPIVOT table ON cols INTO NAME col VALUE col
21096            self.write_keyword(direction);
21097            self.write_space();
21098            self.generate_expression(&pivot.this)?;
21099
21100            if !pivot.expressions.is_empty() {
21101                self.write_space();
21102                self.write_keyword("ON");
21103                self.write_space();
21104                for (i, expr) in pivot.expressions.iter().enumerate() {
21105                    if i > 0 {
21106                        self.write(", ");
21107                    }
21108                    self.generate_expression(expr)?;
21109                }
21110            }
21111
21112            // INTO (for UNPIVOT)
21113            if let Some(into) = &pivot.into {
21114                self.write_space();
21115                self.write_keyword("INTO");
21116                self.write_space();
21117                self.generate_expression(into)?;
21118            }
21119
21120            // USING (for PIVOT)
21121            if !pivot.using.is_empty() {
21122                self.write_space();
21123                self.write_keyword("USING");
21124                self.write_space();
21125                for (i, expr) in pivot.using.iter().enumerate() {
21126                    if i > 0 {
21127                        self.write(", ");
21128                    }
21129                    self.generate_expression(expr)?;
21130                }
21131            }
21132
21133            // GROUP BY
21134            if let Some(group) = &pivot.group {
21135                self.write_space();
21136                self.generate_expression(group)?;
21137            }
21138        } else {
21139            // Standard syntax:
21140            //   table PIVOT(agg [AS alias], ... FOR col IN (val [AS alias], ...) [GROUP BY ...])
21141            //   table UNPIVOT(value_col FOR name_col IN (col1, col2, ...))
21142            // Only output the table expression if it's not a Null (null is used when PIVOT comes after JOIN ON)
21143            if !matches!(&pivot.this, Expression::Null(_)) {
21144                self.generate_expression(&pivot.this)?;
21145                self.write_space();
21146            }
21147            self.write_keyword(direction);
21148            self.write("(");
21149
21150            // Aggregation expressions
21151            for (i, expr) in pivot.expressions.iter().enumerate() {
21152                if i > 0 {
21153                    self.write(", ");
21154                }
21155                self.generate_expression(expr)?;
21156            }
21157
21158            // FOR...IN fields
21159            if !pivot.fields.is_empty() {
21160                if !pivot.expressions.is_empty() {
21161                    self.write_space();
21162                }
21163                self.write_keyword("FOR");
21164                self.write_space();
21165                for (i, field) in pivot.fields.iter().enumerate() {
21166                    if i > 0 {
21167                        self.write_space();
21168                    }
21169                    // field is an In expression: column IN (values)
21170                    self.generate_expression(field)?;
21171                }
21172            }
21173
21174            // DEFAULT ON NULL
21175            if let Some(default_val) = &pivot.default_on_null {
21176                self.write_space();
21177                self.write_keyword("DEFAULT ON NULL");
21178                self.write(" (");
21179                self.generate_expression(default_val)?;
21180                self.write(")");
21181            }
21182
21183            // GROUP BY inside PIVOT parens
21184            if let Some(group) = &pivot.group {
21185                self.write_space();
21186                self.generate_expression(group)?;
21187            }
21188
21189            self.write(")");
21190        }
21191
21192        // Alias
21193        if let Some(alias) = &pivot.alias {
21194            self.write_space();
21195            self.write_keyword("AS");
21196            self.write_space();
21197            self.generate_identifier(alias)?;
21198        }
21199
21200        Ok(())
21201    }
21202
21203    fn generate_unpivot(&mut self, unpivot: &Unpivot) -> Result<()> {
21204        self.generate_expression(&unpivot.this)?;
21205        self.write_space();
21206        self.write_keyword("UNPIVOT");
21207        // Output INCLUDE NULLS or EXCLUDE NULLS if specified
21208        if let Some(include) = unpivot.include_nulls {
21209            self.write_space();
21210            if include {
21211                self.write_keyword("INCLUDE NULLS");
21212            } else {
21213                self.write_keyword("EXCLUDE NULLS");
21214            }
21215            self.write_space();
21216        }
21217        self.write("(");
21218        if unpivot.value_column_parenthesized {
21219            self.write("(");
21220        }
21221        self.generate_identifier(&unpivot.value_column)?;
21222        // Output additional value columns if present
21223        for extra_col in &unpivot.extra_value_columns {
21224            self.write(", ");
21225            self.generate_identifier(extra_col)?;
21226        }
21227        if unpivot.value_column_parenthesized {
21228            self.write(")");
21229        }
21230        self.write_space();
21231        self.write_keyword("FOR");
21232        self.write_space();
21233        self.generate_identifier(&unpivot.name_column)?;
21234        self.write_space();
21235        self.write_keyword("IN");
21236        self.write(" (");
21237        for (i, col) in unpivot.columns.iter().enumerate() {
21238            if i > 0 {
21239                self.write(", ");
21240            }
21241            self.generate_expression(col)?;
21242        }
21243        self.write("))");
21244        if let Some(alias) = &unpivot.alias {
21245            self.write_space();
21246            self.write_keyword("AS");
21247            self.write_space();
21248            self.generate_identifier(alias)?;
21249        }
21250        Ok(())
21251    }
21252
21253    fn generate_values(&mut self, values: &Values) -> Result<()> {
21254        self.write_keyword("VALUES");
21255        for (i, row) in values.expressions.iter().enumerate() {
21256            if i > 0 {
21257                self.write(",");
21258            }
21259            self.write(" (");
21260            for (j, expr) in row.expressions.iter().enumerate() {
21261                if j > 0 {
21262                    self.write(", ");
21263                }
21264                self.generate_expression(expr)?;
21265            }
21266            self.write(")");
21267        }
21268        if let Some(alias) = &values.alias {
21269            self.write_space();
21270            self.write_keyword("AS");
21271            self.write_space();
21272            self.generate_identifier(alias)?;
21273            if !values.column_aliases.is_empty() {
21274                self.write("(");
21275                for (i, col) in values.column_aliases.iter().enumerate() {
21276                    if i > 0 {
21277                        self.write(", ");
21278                    }
21279                    self.generate_identifier(col)?;
21280                }
21281                self.write(")");
21282            }
21283        }
21284        Ok(())
21285    }
21286
21287    fn generate_array(&mut self, arr: &Array) -> Result<()> {
21288        // Apply struct name inheritance for target dialects that need it
21289        let needs_inheritance = matches!(
21290            self.config.dialect,
21291            Some(DialectType::DuckDB)
21292                | Some(DialectType::Spark)
21293                | Some(DialectType::Databricks)
21294                | Some(DialectType::Hive)
21295                | Some(DialectType::Snowflake)
21296                | Some(DialectType::Presto)
21297                | Some(DialectType::Trino)
21298        );
21299        let propagated: Vec<Expression>;
21300        let expressions = if needs_inheritance && arr.expressions.len() > 1 {
21301            propagated = Self::inherit_struct_field_names(&arr.expressions);
21302            &propagated
21303        } else {
21304            &arr.expressions
21305        };
21306
21307        // Generic mode: ARRAY(1, 2, 3) with parentheses
21308        // Dialect mode: ARRAY[1, 2, 3] with brackets (or just [1, 2, 3] if array_bracket_only)
21309        let use_parens =
21310            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
21311        if !self.config.array_bracket_only {
21312            self.write_keyword("ARRAY");
21313        }
21314        if use_parens {
21315            self.write("(");
21316        } else {
21317            self.write("[");
21318        }
21319        for (i, expr) in expressions.iter().enumerate() {
21320            if i > 0 {
21321                self.write(", ");
21322            }
21323            self.generate_expression(expr)?;
21324        }
21325        if use_parens {
21326            self.write(")");
21327        } else {
21328            self.write("]");
21329        }
21330        Ok(())
21331    }
21332
21333    fn generate_tuple(&mut self, tuple: &Tuple) -> Result<()> {
21334        // Special case: Tuple(function/expr, TableAlias) pattern for table functions with typed aliases
21335        // Used for PostgreSQL functions like JSON_TO_RECORDSET: FUNC(args) AS alias(col1 type1, col2 type2)
21336        if tuple.expressions.len() == 2 {
21337            if let Expression::TableAlias(_) = &tuple.expressions[1] {
21338                // First element is the function/expression, second is the TableAlias
21339                self.generate_expression(&tuple.expressions[0])?;
21340                self.write_space();
21341                self.write_keyword("AS");
21342                self.write_space();
21343                self.generate_expression(&tuple.expressions[1])?;
21344                return Ok(());
21345            }
21346        }
21347
21348        // In pretty mode, format long tuples with each element on a new line
21349        // Only expand if total width exceeds threshold
21350        let expand_tuple = if self.config.pretty && tuple.expressions.len() > 1 {
21351            let mut expr_strings: Vec<String> = Vec::with_capacity(tuple.expressions.len());
21352            for expr in &tuple.expressions {
21353                expr_strings.push(self.generate_to_string(expr)?);
21354            }
21355            self.too_wide(&expr_strings)
21356        } else {
21357            false
21358        };
21359
21360        if expand_tuple {
21361            self.write("(");
21362            self.write_newline();
21363            self.indent_level += 1;
21364            for (i, expr) in tuple.expressions.iter().enumerate() {
21365                if i > 0 {
21366                    self.write(",");
21367                    self.write_newline();
21368                }
21369                self.write_indent();
21370                self.generate_expression(expr)?;
21371            }
21372            self.indent_level -= 1;
21373            self.write_newline();
21374            self.write_indent();
21375            self.write(")");
21376        } else {
21377            self.write("(");
21378            for (i, expr) in tuple.expressions.iter().enumerate() {
21379                if i > 0 {
21380                    self.write(", ");
21381                }
21382                self.generate_expression(expr)?;
21383            }
21384            self.write(")");
21385        }
21386        Ok(())
21387    }
21388
21389    fn generate_pipe_operator(&mut self, pipe: &PipeOperator) -> Result<()> {
21390        self.generate_expression(&pipe.this)?;
21391        self.write(" |> ");
21392        self.generate_expression(&pipe.expression)?;
21393        Ok(())
21394    }
21395
21396    fn generate_ordered(&mut self, ordered: &Ordered) -> Result<()> {
21397        self.generate_expression(&ordered.this)?;
21398        if ordered.desc {
21399            self.write_space();
21400            self.write_keyword("DESC");
21401        } else if ordered.explicit_asc {
21402            self.write_space();
21403            self.write_keyword("ASC");
21404        }
21405        if let Some(nulls_first) = ordered.nulls_first {
21406            // Determine if we should skip outputting NULLS FIRST/LAST when it's the default
21407            // for the dialect. Different dialects have different NULL ordering defaults:
21408            //
21409            // nulls_are_large (Oracle, Postgres, Snowflake, etc.):
21410            //   - ASC: NULLS LAST is default (omit NULLS LAST for ASC)
21411            //   - DESC: NULLS FIRST is default (omit NULLS FIRST for DESC)
21412            //
21413            // nulls_are_small (Spark, Hive, BigQuery, most others):
21414            //   - ASC: NULLS FIRST is default
21415            //   - DESC: NULLS LAST is default
21416            //
21417            // nulls_are_last (DuckDB, Presto, Trino, Dremio, etc.):
21418            //   - NULLS LAST is always the default regardless of sort direction
21419            let is_asc = !ordered.desc;
21420            let is_nulls_are_large = matches!(
21421                self.config.dialect,
21422                Some(DialectType::Oracle)
21423                    | Some(DialectType::PostgreSQL)
21424                    | Some(DialectType::Redshift)
21425                    | Some(DialectType::Snowflake)
21426            );
21427            let is_nulls_are_last = matches!(
21428                self.config.dialect,
21429                Some(DialectType::Dremio)
21430                    | Some(DialectType::DuckDB)
21431                    | Some(DialectType::Presto)
21432                    | Some(DialectType::Trino)
21433                    | Some(DialectType::Athena)
21434                    | Some(DialectType::ClickHouse)
21435                    | Some(DialectType::Drill)
21436                    | Some(DialectType::Exasol)
21437            );
21438
21439            // Check if the NULLS ordering matches the default for this dialect
21440            let is_default_nulls = if is_nulls_are_large {
21441                // For nulls_are_large: ASC + NULLS LAST or DESC + NULLS FIRST is default
21442                (is_asc && !nulls_first) || (!is_asc && nulls_first)
21443            } else if is_nulls_are_last {
21444                // For nulls_are_last: NULLS LAST is always default
21445                !nulls_first
21446            } else {
21447                false
21448            };
21449
21450            if !is_default_nulls {
21451                self.write_space();
21452                self.write_keyword("NULLS");
21453                self.write_space();
21454                self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
21455            }
21456        }
21457        // WITH FILL clause (ClickHouse)
21458        if let Some(ref with_fill) = ordered.with_fill {
21459            self.write_space();
21460            self.generate_with_fill(with_fill)?;
21461        }
21462        Ok(())
21463    }
21464
21465    /// Write a ClickHouse type string, wrapping in Nullable unless in map key context.
21466    fn write_clickhouse_type(&mut self, type_str: &str) {
21467        if self.clickhouse_nullable_depth < 0 {
21468            // Map key context: don't wrap in Nullable
21469            self.write(type_str);
21470        } else {
21471            self.write(&format!("Nullable({})", type_str));
21472        }
21473    }
21474
21475    fn generate_data_type(&mut self, dt: &DataType) -> Result<()> {
21476        use crate::dialects::DialectType;
21477
21478        match dt {
21479            DataType::Boolean => {
21480                // Dialect-specific boolean type mappings
21481                match self.config.dialect {
21482                    Some(DialectType::TSQL) => self.write_keyword("BIT"),
21483                    Some(DialectType::MySQL) => self.write_keyword("BOOLEAN"), // alias for TINYINT(1)
21484                    Some(DialectType::Oracle) => {
21485                        // Oracle 23c+ supports BOOLEAN, older versions use NUMBER(1)
21486                        self.write_keyword("NUMBER(1)")
21487                    }
21488                    Some(DialectType::ClickHouse) => self.write("Bool"), // ClickHouse uses Bool (case-sensitive)
21489                    _ => self.write_keyword("BOOLEAN"),
21490                }
21491            }
21492            DataType::TinyInt { length } => {
21493                // PostgreSQL, Oracle, and Exasol don't have TINYINT, use SMALLINT
21494                // Dremio maps TINYINT to INT
21495                // ClickHouse maps TINYINT to Int8
21496                match self.config.dialect {
21497                    Some(DialectType::PostgreSQL)
21498                    | Some(DialectType::Redshift)
21499                    | Some(DialectType::Oracle)
21500                    | Some(DialectType::Exasol) => {
21501                        self.write_keyword("SMALLINT");
21502                    }
21503                    Some(DialectType::Teradata) => {
21504                        // Teradata uses BYTEINT for smallest integer
21505                        self.write_keyword("BYTEINT");
21506                    }
21507                    Some(DialectType::Dremio) => {
21508                        // Dremio maps TINYINT to INT
21509                        self.write_keyword("INT");
21510                    }
21511                    Some(DialectType::ClickHouse) => {
21512                        self.write_clickhouse_type("Int8");
21513                    }
21514                    _ => {
21515                        self.write_keyword("TINYINT");
21516                    }
21517                }
21518                if let Some(n) = length {
21519                    if !matches!(
21520                        self.config.dialect,
21521                        Some(DialectType::Dremio) | Some(DialectType::ClickHouse)
21522                    ) {
21523                        self.write(&format!("({})", n));
21524                    }
21525                }
21526            }
21527            DataType::SmallInt { length } => {
21528                // Dremio maps SMALLINT to INT, SQLite/Drill maps SMALLINT to INTEGER
21529                match self.config.dialect {
21530                    Some(DialectType::Dremio) => {
21531                        self.write_keyword("INT");
21532                    }
21533                    Some(DialectType::SQLite) | Some(DialectType::Drill) => {
21534                        self.write_keyword("INTEGER");
21535                    }
21536                    Some(DialectType::BigQuery) => {
21537                        self.write_keyword("INT64");
21538                    }
21539                    Some(DialectType::ClickHouse) => {
21540                        self.write_clickhouse_type("Int16");
21541                    }
21542                    _ => {
21543                        self.write_keyword("SMALLINT");
21544                        if let Some(n) = length {
21545                            self.write(&format!("({})", n));
21546                        }
21547                    }
21548                }
21549            }
21550            DataType::Int {
21551                length,
21552                integer_spelling,
21553            } => {
21554                // BigQuery uses INT64 for INT
21555                if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
21556                    self.write_keyword("INT64");
21557                } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
21558                    self.write_clickhouse_type("Int32");
21559                } else {
21560                    // TSQL, Presto, Trino, SQLite, Redshift use INTEGER as the canonical form
21561                    let use_integer = match self.config.dialect {
21562                        Some(DialectType::TSQL)
21563                        | Some(DialectType::Fabric)
21564                        | Some(DialectType::Presto)
21565                        | Some(DialectType::Trino)
21566                        | Some(DialectType::SQLite)
21567                        | Some(DialectType::Redshift) => true,
21568                        // Databricks preserves the original spelling
21569                        Some(DialectType::Databricks) => *integer_spelling,
21570                        _ => false,
21571                    };
21572                    if use_integer {
21573                        self.write_keyword("INTEGER");
21574                    } else {
21575                        self.write_keyword("INT");
21576                    }
21577                    if let Some(n) = length {
21578                        self.write(&format!("({})", n));
21579                    }
21580                }
21581            }
21582            DataType::BigInt { length } => {
21583                // Dialect-specific bigint type mappings
21584                match self.config.dialect {
21585                    Some(DialectType::Oracle) => {
21586                        // Oracle doesn't have BIGINT, uses INT
21587                        self.write_keyword("INT");
21588                    }
21589                    Some(DialectType::ClickHouse) => {
21590                        self.write_clickhouse_type("Int64");
21591                    }
21592                    _ => {
21593                        self.write_keyword("BIGINT");
21594                        if let Some(n) = length {
21595                            self.write(&format!("({})", n));
21596                        }
21597                    }
21598                }
21599            }
21600            DataType::Float {
21601                precision,
21602                scale,
21603                real_spelling,
21604            } => {
21605                // Dialect-specific float type mappings
21606                // If real_spelling is true, preserve REAL; otherwise use dialect default
21607                // Spark/Hive don't support REAL, always use FLOAT
21608                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
21609                    self.write_clickhouse_type("Float32");
21610                } else if *real_spelling
21611                    && !matches!(
21612                        self.config.dialect,
21613                        Some(DialectType::Spark)
21614                            | Some(DialectType::Databricks)
21615                            | Some(DialectType::Hive)
21616                            | Some(DialectType::Snowflake)
21617                            | Some(DialectType::MySQL)
21618                            | Some(DialectType::BigQuery)
21619                    )
21620                {
21621                    self.write_keyword("REAL")
21622                } else {
21623                    match self.config.dialect {
21624                        Some(DialectType::PostgreSQL) => self.write_keyword("REAL"),
21625                        Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
21626                        _ => self.write_keyword("FLOAT"),
21627                    }
21628                }
21629                // MySQL supports FLOAT(precision) or FLOAT(precision, scale)
21630                // Spark/Hive don't support FLOAT(precision)
21631                if !matches!(
21632                    self.config.dialect,
21633                    Some(DialectType::Spark)
21634                        | Some(DialectType::Databricks)
21635                        | Some(DialectType::Hive)
21636                        | Some(DialectType::Presto)
21637                        | Some(DialectType::Trino)
21638                ) {
21639                    if let Some(p) = precision {
21640                        self.write(&format!("({}", p));
21641                        if let Some(s) = scale {
21642                            self.write(&format!(", {})", s));
21643                        } else {
21644                            self.write(")");
21645                        }
21646                    }
21647                }
21648            }
21649            DataType::Double { precision, scale } => {
21650                // Dialect-specific double type mappings
21651                match self.config.dialect {
21652                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
21653                        self.write_keyword("FLOAT")
21654                    } // SQL Server/Fabric FLOAT is double
21655                    Some(DialectType::Oracle) => self.write_keyword("DOUBLE PRECISION"),
21656                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("Float64"),
21657                    Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
21658                    Some(DialectType::SQLite) => self.write_keyword("REAL"),
21659                    Some(DialectType::PostgreSQL)
21660                    | Some(DialectType::Redshift)
21661                    | Some(DialectType::Teradata)
21662                    | Some(DialectType::Materialize) => self.write_keyword("DOUBLE PRECISION"),
21663                    _ => self.write_keyword("DOUBLE"),
21664                }
21665                // MySQL supports DOUBLE(precision, scale)
21666                if let Some(p) = precision {
21667                    self.write(&format!("({}", p));
21668                    if let Some(s) = scale {
21669                        self.write(&format!(", {})", s));
21670                    } else {
21671                        self.write(")");
21672                    }
21673                }
21674            }
21675            DataType::Decimal { precision, scale } => {
21676                // Dialect-specific decimal type mappings
21677                match self.config.dialect {
21678                    Some(DialectType::ClickHouse) => {
21679                        self.write("Decimal");
21680                        if let Some(p) = precision {
21681                            self.write(&format!("({}", p));
21682                            if let Some(s) = scale {
21683                                self.write(&format!(", {}", s));
21684                            }
21685                            self.write(")");
21686                        }
21687                    }
21688                    Some(DialectType::Oracle) => {
21689                        // Oracle uses NUMBER instead of DECIMAL
21690                        self.write_keyword("NUMBER");
21691                        if let Some(p) = precision {
21692                            self.write(&format!("({}", p));
21693                            if let Some(s) = scale {
21694                                self.write(&format!(", {}", s));
21695                            }
21696                            self.write(")");
21697                        }
21698                    }
21699                    Some(DialectType::BigQuery) => {
21700                        // BigQuery uses NUMERIC instead of DECIMAL
21701                        self.write_keyword("NUMERIC");
21702                        if let Some(p) = precision {
21703                            self.write(&format!("({}", p));
21704                            if let Some(s) = scale {
21705                                self.write(&format!(", {}", s));
21706                            }
21707                            self.write(")");
21708                        }
21709                    }
21710                    _ => {
21711                        self.write_keyword("DECIMAL");
21712                        if let Some(p) = precision {
21713                            self.write(&format!("({}", p));
21714                            if let Some(s) = scale {
21715                                self.write(&format!(", {}", s));
21716                            }
21717                            self.write(")");
21718                        }
21719                    }
21720                }
21721            }
21722            DataType::Char { length } => {
21723                // Dialect-specific char type mappings
21724                match self.config.dialect {
21725                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
21726                        // DuckDB/SQLite maps CHAR to TEXT
21727                        self.write_keyword("TEXT");
21728                    }
21729                    Some(DialectType::Hive)
21730                    | Some(DialectType::Spark)
21731                    | Some(DialectType::Databricks) => {
21732                        // Hive/Spark/Databricks maps CHAR to STRING (when no length)
21733                        // CHAR(n) with explicit length is kept as CHAR(n) for Spark/Databricks
21734                        if length.is_some()
21735                            && !matches!(self.config.dialect, Some(DialectType::Hive))
21736                        {
21737                            self.write_keyword("CHAR");
21738                            if let Some(n) = length {
21739                                self.write(&format!("({})", n));
21740                            }
21741                        } else {
21742                            self.write_keyword("STRING");
21743                        }
21744                    }
21745                    Some(DialectType::Dremio) => {
21746                        // Dremio maps CHAR to VARCHAR
21747                        self.write_keyword("VARCHAR");
21748                        if let Some(n) = length {
21749                            self.write(&format!("({})", n));
21750                        }
21751                    }
21752                    _ => {
21753                        self.write_keyword("CHAR");
21754                        if let Some(n) = length {
21755                            self.write(&format!("({})", n));
21756                        }
21757                    }
21758                }
21759            }
21760            DataType::VarChar {
21761                length,
21762                parenthesized_length,
21763            } => {
21764                // Dialect-specific varchar type mappings
21765                match self.config.dialect {
21766                    Some(DialectType::Oracle) => {
21767                        self.write_keyword("VARCHAR2");
21768                        if let Some(n) = length {
21769                            self.write(&format!("({})", n));
21770                        }
21771                    }
21772                    Some(DialectType::DuckDB) => {
21773                        // DuckDB maps VARCHAR to TEXT, preserving length
21774                        self.write_keyword("TEXT");
21775                        if let Some(n) = length {
21776                            self.write(&format!("({})", n));
21777                        }
21778                    }
21779                    Some(DialectType::SQLite) => {
21780                        // SQLite maps VARCHAR to TEXT, preserving length
21781                        self.write_keyword("TEXT");
21782                        if let Some(n) = length {
21783                            self.write(&format!("({})", n));
21784                        }
21785                    }
21786                    Some(DialectType::MySQL) if length.is_none() => {
21787                        // MySQL requires VARCHAR to have a size - if it doesn't, use TEXT
21788                        self.write_keyword("TEXT");
21789                    }
21790                    Some(DialectType::Hive)
21791                    | Some(DialectType::Spark)
21792                    | Some(DialectType::Databricks)
21793                        if length.is_none() =>
21794                    {
21795                        // Hive/Spark/Databricks: VARCHAR without length → STRING
21796                        self.write_keyword("STRING");
21797                    }
21798                    _ => {
21799                        self.write_keyword("VARCHAR");
21800                        if let Some(n) = length {
21801                            // Hive uses VARCHAR((n)) with extra parentheses in STRUCT definitions
21802                            if *parenthesized_length {
21803                                self.write(&format!("(({}))", n));
21804                            } else {
21805                                self.write(&format!("({})", n));
21806                            }
21807                        }
21808                    }
21809                }
21810            }
21811            DataType::Text => {
21812                // Dialect-specific text type mappings
21813                match self.config.dialect {
21814                    Some(DialectType::Oracle) => self.write_keyword("CLOB"),
21815                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
21816                        self.write_keyword("VARCHAR(MAX)")
21817                    }
21818                    Some(DialectType::BigQuery) => self.write_keyword("STRING"),
21819                    Some(DialectType::Snowflake)
21820                    | Some(DialectType::Dremio)
21821                    | Some(DialectType::Drill) => self.write_keyword("VARCHAR"),
21822                    Some(DialectType::Exasol) => self.write_keyword("LONG VARCHAR"),
21823                    Some(DialectType::Presto)
21824                    | Some(DialectType::Trino)
21825                    | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
21826                    Some(DialectType::Spark)
21827                    | Some(DialectType::Databricks)
21828                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
21829                    Some(DialectType::Redshift) => self.write_keyword("VARCHAR(MAX)"),
21830                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
21831                        self.write_keyword("STRING")
21832                    }
21833                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
21834                    _ => self.write_keyword("TEXT"),
21835                }
21836            }
21837            DataType::TextWithLength { length } => {
21838                // TEXT(n) - dialect-specific type with length
21839                match self.config.dialect {
21840                    Some(DialectType::Oracle) => self.write(&format!("CLOB({})", length)),
21841                    Some(DialectType::Hive)
21842                    | Some(DialectType::Spark)
21843                    | Some(DialectType::Databricks) => {
21844                        self.write(&format!("VARCHAR({})", length));
21845                    }
21846                    Some(DialectType::Redshift) => self.write(&format!("VARCHAR({})", length)),
21847                    Some(DialectType::BigQuery) => self.write(&format!("STRING({})", length)),
21848                    Some(DialectType::Snowflake)
21849                    | Some(DialectType::Presto)
21850                    | Some(DialectType::Trino)
21851                    | Some(DialectType::Athena)
21852                    | Some(DialectType::Drill)
21853                    | Some(DialectType::Dremio) => {
21854                        self.write(&format!("VARCHAR({})", length));
21855                    }
21856                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
21857                        self.write(&format!("VARCHAR({})", length))
21858                    }
21859                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
21860                        self.write(&format!("STRING({})", length))
21861                    }
21862                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
21863                    _ => self.write(&format!("TEXT({})", length)),
21864                }
21865            }
21866            DataType::String { length } => {
21867                // STRING type with optional length (BigQuery STRING(n))
21868                match self.config.dialect {
21869                    Some(DialectType::ClickHouse) => {
21870                        // ClickHouse uses String with specific casing
21871                        self.write("String");
21872                        if let Some(n) = length {
21873                            self.write(&format!("({})", n));
21874                        }
21875                    }
21876                    Some(DialectType::BigQuery)
21877                    | Some(DialectType::Hive)
21878                    | Some(DialectType::Spark)
21879                    | Some(DialectType::Databricks)
21880                    | Some(DialectType::StarRocks)
21881                    | Some(DialectType::Doris) => {
21882                        self.write_keyword("STRING");
21883                        if let Some(n) = length {
21884                            self.write(&format!("({})", n));
21885                        }
21886                    }
21887                    Some(DialectType::PostgreSQL) => {
21888                        // PostgreSQL doesn't have STRING - use VARCHAR or TEXT
21889                        if let Some(n) = length {
21890                            self.write_keyword("VARCHAR");
21891                            self.write(&format!("({})", n));
21892                        } else {
21893                            self.write_keyword("TEXT");
21894                        }
21895                    }
21896                    Some(DialectType::Redshift) => {
21897                        // Redshift: STRING -> VARCHAR(MAX)
21898                        if let Some(n) = length {
21899                            self.write_keyword("VARCHAR");
21900                            self.write(&format!("({})", n));
21901                        } else {
21902                            self.write_keyword("VARCHAR(MAX)");
21903                        }
21904                    }
21905                    Some(DialectType::MySQL) => {
21906                        // MySQL doesn't have STRING - use VARCHAR or TEXT
21907                        if let Some(n) = length {
21908                            self.write_keyword("VARCHAR");
21909                            self.write(&format!("({})", n));
21910                        } else {
21911                            self.write_keyword("TEXT");
21912                        }
21913                    }
21914                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
21915                        // TSQL: STRING -> VARCHAR(MAX)
21916                        if let Some(n) = length {
21917                            self.write_keyword("VARCHAR");
21918                            self.write(&format!("({})", n));
21919                        } else {
21920                            self.write_keyword("VARCHAR(MAX)");
21921                        }
21922                    }
21923                    Some(DialectType::Oracle) => {
21924                        // Oracle: STRING -> CLOB
21925                        self.write_keyword("CLOB");
21926                    }
21927                    Some(DialectType::DuckDB) | Some(DialectType::Materialize) => {
21928                        // DuckDB/Materialize uses TEXT for string types
21929                        self.write_keyword("TEXT");
21930                        if let Some(n) = length {
21931                            self.write(&format!("({})", n));
21932                        }
21933                    }
21934                    Some(DialectType::Presto)
21935                    | Some(DialectType::Trino)
21936                    | Some(DialectType::Drill)
21937                    | Some(DialectType::Dremio) => {
21938                        // Presto/Trino/Drill use VARCHAR for string types
21939                        self.write_keyword("VARCHAR");
21940                        if let Some(n) = length {
21941                            self.write(&format!("({})", n));
21942                        }
21943                    }
21944                    Some(DialectType::Snowflake) => {
21945                        // Snowflake: STRING stays as STRING (identity/DDL)
21946                        // CAST context STRING -> VARCHAR is handled in generate_cast
21947                        self.write_keyword("STRING");
21948                        if let Some(n) = length {
21949                            self.write(&format!("({})", n));
21950                        }
21951                    }
21952                    _ => {
21953                        // Default: output STRING with optional length
21954                        self.write_keyword("STRING");
21955                        if let Some(n) = length {
21956                            self.write(&format!("({})", n));
21957                        }
21958                    }
21959                }
21960            }
21961            DataType::Binary { length } => {
21962                // Dialect-specific binary type mappings
21963                match self.config.dialect {
21964                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
21965                        self.write_keyword("BYTEA");
21966                        if let Some(n) = length {
21967                            self.write(&format!("({})", n));
21968                        }
21969                    }
21970                    Some(DialectType::Redshift) => {
21971                        self.write_keyword("VARBYTE");
21972                        if let Some(n) = length {
21973                            self.write(&format!("({})", n));
21974                        }
21975                    }
21976                    Some(DialectType::DuckDB)
21977                    | Some(DialectType::SQLite)
21978                    | Some(DialectType::Oracle) => {
21979                        // DuckDB/SQLite/Oracle maps BINARY to BLOB
21980                        self.write_keyword("BLOB");
21981                        if let Some(n) = length {
21982                            self.write(&format!("({})", n));
21983                        }
21984                    }
21985                    Some(DialectType::Presto)
21986                    | Some(DialectType::Trino)
21987                    | Some(DialectType::Athena)
21988                    | Some(DialectType::Drill)
21989                    | Some(DialectType::Dremio) => {
21990                        // These dialects map BINARY to VARBINARY
21991                        self.write_keyword("VARBINARY");
21992                        if let Some(n) = length {
21993                            self.write(&format!("({})", n));
21994                        }
21995                    }
21996                    Some(DialectType::ClickHouse) => {
21997                        // ClickHouse: wrap BINARY in Nullable (unless map key context)
21998                        if self.clickhouse_nullable_depth < 0 {
21999                            self.write("BINARY");
22000                        } else {
22001                            self.write("Nullable(BINARY");
22002                        }
22003                        if let Some(n) = length {
22004                            self.write(&format!("({})", n));
22005                        }
22006                        if self.clickhouse_nullable_depth >= 0 {
22007                            self.write(")");
22008                        }
22009                    }
22010                    _ => {
22011                        self.write_keyword("BINARY");
22012                        if let Some(n) = length {
22013                            self.write(&format!("({})", n));
22014                        }
22015                    }
22016                }
22017            }
22018            DataType::VarBinary { length } => {
22019                // Dialect-specific varbinary type mappings
22020                match self.config.dialect {
22021                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
22022                        self.write_keyword("BYTEA");
22023                        if let Some(n) = length {
22024                            self.write(&format!("({})", n));
22025                        }
22026                    }
22027                    Some(DialectType::Redshift) => {
22028                        self.write_keyword("VARBYTE");
22029                        if let Some(n) = length {
22030                            self.write(&format!("({})", n));
22031                        }
22032                    }
22033                    Some(DialectType::DuckDB)
22034                    | Some(DialectType::SQLite)
22035                    | Some(DialectType::Oracle) => {
22036                        // DuckDB/SQLite/Oracle maps VARBINARY to BLOB
22037                        self.write_keyword("BLOB");
22038                        if let Some(n) = length {
22039                            self.write(&format!("({})", n));
22040                        }
22041                    }
22042                    Some(DialectType::Exasol) => {
22043                        // Exasol maps VARBINARY to VARCHAR
22044                        self.write_keyword("VARCHAR");
22045                    }
22046                    Some(DialectType::Spark)
22047                    | Some(DialectType::Hive)
22048                    | Some(DialectType::Databricks) => {
22049                        // Spark/Hive use BINARY instead of VARBINARY
22050                        self.write_keyword("BINARY");
22051                        if let Some(n) = length {
22052                            self.write(&format!("({})", n));
22053                        }
22054                    }
22055                    Some(DialectType::ClickHouse) => {
22056                        // ClickHouse maps VARBINARY to String (wrapped in Nullable unless map key)
22057                        self.write_clickhouse_type("String");
22058                    }
22059                    _ => {
22060                        self.write_keyword("VARBINARY");
22061                        if let Some(n) = length {
22062                            self.write(&format!("({})", n));
22063                        }
22064                    }
22065                }
22066            }
22067            DataType::Blob => {
22068                // Dialect-specific blob type mappings
22069                match self.config.dialect {
22070                    Some(DialectType::PostgreSQL) => self.write_keyword("BYTEA"),
22071                    Some(DialectType::Redshift) => self.write_keyword("VARBYTE"),
22072                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
22073                        self.write_keyword("VARBINARY")
22074                    }
22075                    Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
22076                    Some(DialectType::Exasol) => self.write_keyword("VARCHAR"),
22077                    Some(DialectType::Presto)
22078                    | Some(DialectType::Trino)
22079                    | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
22080                    Some(DialectType::DuckDB) => {
22081                        // Python sqlglot: BLOB -> VARBINARY for DuckDB (base TYPE_MAPPING)
22082                        // DuckDB identity works via: BLOB -> transform VarBinary -> generator BLOB
22083                        self.write_keyword("VARBINARY");
22084                    }
22085                    Some(DialectType::Spark)
22086                    | Some(DialectType::Databricks)
22087                    | Some(DialectType::Hive) => self.write_keyword("BINARY"),
22088                    Some(DialectType::ClickHouse) => {
22089                        // BLOB maps to Nullable(String) in ClickHouse, even in column defs
22090                        // where we normally suppress Nullable wrapping (clickhouse_nullable_depth = -1).
22091                        // This matches Python sqlglot behavior.
22092                        self.write("Nullable(String)");
22093                    }
22094                    _ => self.write_keyword("BLOB"),
22095                }
22096            }
22097            DataType::Bit { length } => {
22098                // Dialect-specific bit type mappings
22099                match self.config.dialect {
22100                    Some(DialectType::Dremio)
22101                    | Some(DialectType::Spark)
22102                    | Some(DialectType::Databricks)
22103                    | Some(DialectType::Hive)
22104                    | Some(DialectType::Snowflake)
22105                    | Some(DialectType::BigQuery)
22106                    | Some(DialectType::Presto)
22107                    | Some(DialectType::Trino)
22108                    | Some(DialectType::ClickHouse)
22109                    | Some(DialectType::Redshift) => {
22110                        // These dialects don't support BIT type, use BOOLEAN
22111                        self.write_keyword("BOOLEAN");
22112                    }
22113                    _ => {
22114                        self.write_keyword("BIT");
22115                        if let Some(n) = length {
22116                            self.write(&format!("({})", n));
22117                        }
22118                    }
22119                }
22120            }
22121            DataType::VarBit { length } => {
22122                self.write_keyword("VARBIT");
22123                if let Some(n) = length {
22124                    self.write(&format!("({})", n));
22125                }
22126            }
22127            DataType::Date => self.write_keyword("DATE"),
22128            DataType::Time {
22129                precision,
22130                timezone,
22131            } => {
22132                if *timezone {
22133                    // Dialect-specific TIME WITH TIME ZONE output
22134                    match self.config.dialect {
22135                        Some(DialectType::DuckDB) => {
22136                            // DuckDB: TIMETZ (drops precision)
22137                            self.write_keyword("TIMETZ");
22138                        }
22139                        Some(DialectType::PostgreSQL) => {
22140                            // PostgreSQL: TIMETZ or TIMETZ(p)
22141                            self.write_keyword("TIMETZ");
22142                            if let Some(p) = precision {
22143                                self.write(&format!("({})", p));
22144                            }
22145                        }
22146                        _ => {
22147                            // Presto/Trino/Redshift/others: TIME(p) WITH TIME ZONE
22148                            self.write_keyword("TIME");
22149                            if let Some(p) = precision {
22150                                self.write(&format!("({})", p));
22151                            }
22152                            self.write_keyword(" WITH TIME ZONE");
22153                        }
22154                    }
22155                } else {
22156                    // Spark/Hive/Databricks: TIME -> TIMESTAMP (TIME not supported)
22157                    if matches!(
22158                        self.config.dialect,
22159                        Some(DialectType::Spark)
22160                            | Some(DialectType::Databricks)
22161                            | Some(DialectType::Hive)
22162                    ) {
22163                        self.write_keyword("TIMESTAMP");
22164                    } else {
22165                        self.write_keyword("TIME");
22166                        if let Some(p) = precision {
22167                            self.write(&format!("({})", p));
22168                        }
22169                    }
22170                }
22171            }
22172            DataType::Timestamp {
22173                precision,
22174                timezone,
22175            } => {
22176                // Dialect-specific timestamp type mappings
22177                match self.config.dialect {
22178                    Some(DialectType::ClickHouse) => {
22179                        self.write("DateTime");
22180                        if let Some(p) = precision {
22181                            self.write(&format!("({})", p));
22182                        }
22183                    }
22184                    Some(DialectType::TSQL) => {
22185                        if *timezone {
22186                            self.write_keyword("DATETIMEOFFSET");
22187                        } else {
22188                            self.write_keyword("DATETIME2");
22189                        }
22190                        if let Some(p) = precision {
22191                            self.write(&format!("({})", p));
22192                        }
22193                    }
22194                    Some(DialectType::MySQL) => {
22195                        // MySQL: TIMESTAMP stays as TIMESTAMP in DDL; CAST mapping handled separately
22196                        self.write_keyword("TIMESTAMP");
22197                        if let Some(p) = precision {
22198                            self.write(&format!("({})", p));
22199                        }
22200                    }
22201                    Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
22202                        // Doris/StarRocks: TIMESTAMP -> DATETIME
22203                        self.write_keyword("DATETIME");
22204                        if let Some(p) = precision {
22205                            self.write(&format!("({})", p));
22206                        }
22207                    }
22208                    Some(DialectType::BigQuery) => {
22209                        // BigQuery: TIMESTAMP is always UTC, DATETIME is timezone-naive
22210                        if *timezone {
22211                            self.write_keyword("TIMESTAMP");
22212                        } else {
22213                            self.write_keyword("DATETIME");
22214                        }
22215                    }
22216                    Some(DialectType::DuckDB) => {
22217                        // DuckDB: TIMESTAMPTZ shorthand
22218                        if *timezone {
22219                            self.write_keyword("TIMESTAMPTZ");
22220                        } else {
22221                            self.write_keyword("TIMESTAMP");
22222                            if let Some(p) = precision {
22223                                self.write(&format!("({})", p));
22224                            }
22225                        }
22226                    }
22227                    _ => {
22228                        if *timezone && !self.config.tz_to_with_time_zone {
22229                            // Use TIMESTAMPTZ shorthand when dialect doesn't prefer WITH TIME ZONE
22230                            self.write_keyword("TIMESTAMPTZ");
22231                            if let Some(p) = precision {
22232                                self.write(&format!("({})", p));
22233                            }
22234                        } else {
22235                            self.write_keyword("TIMESTAMP");
22236                            if let Some(p) = precision {
22237                                self.write(&format!("({})", p));
22238                            }
22239                            if *timezone {
22240                                self.write_space();
22241                                self.write_keyword("WITH TIME ZONE");
22242                            }
22243                        }
22244                    }
22245                }
22246            }
22247            DataType::Interval { unit, to } => {
22248                self.write_keyword("INTERVAL");
22249                if let Some(u) = unit {
22250                    self.write_space();
22251                    self.write_keyword(u);
22252                }
22253                // Handle range intervals like DAY TO HOUR
22254                if let Some(t) = to {
22255                    self.write_space();
22256                    self.write_keyword("TO");
22257                    self.write_space();
22258                    self.write_keyword(t);
22259                }
22260            }
22261            DataType::Json => {
22262                // Dialect-specific JSON type mappings
22263                match self.config.dialect {
22264                    Some(DialectType::Oracle) => self.write_keyword("JSON"), // Oracle 21c+
22265                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"), // No native JSON type
22266                    Some(DialectType::MySQL) => self.write_keyword("JSON"),
22267                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
22268                    _ => self.write_keyword("JSON"),
22269                }
22270            }
22271            DataType::JsonB => {
22272                // JSONB is PostgreSQL specific, but Doris also supports it
22273                match self.config.dialect {
22274                    Some(DialectType::PostgreSQL) => self.write_keyword("JSONB"),
22275                    Some(DialectType::Doris) => self.write_keyword("JSONB"),
22276                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
22277                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
22278                    Some(DialectType::DuckDB) => self.write_keyword("JSON"), // DuckDB maps JSONB to JSON
22279                    _ => self.write_keyword("JSON"), // Fall back to JSON for other dialects
22280                }
22281            }
22282            DataType::Uuid => {
22283                // Dialect-specific UUID type mappings
22284                match self.config.dialect {
22285                    Some(DialectType::TSQL) => self.write_keyword("UNIQUEIDENTIFIER"),
22286                    Some(DialectType::MySQL) => self.write_keyword("CHAR(36)"),
22287                    Some(DialectType::Oracle) => self.write_keyword("RAW(16)"),
22288                    Some(DialectType::BigQuery)
22289                    | Some(DialectType::Spark)
22290                    | Some(DialectType::Databricks) => self.write_keyword("STRING"),
22291                    _ => self.write_keyword("UUID"),
22292                }
22293            }
22294            DataType::Array {
22295                element_type,
22296                dimension,
22297            } => {
22298                // Dialect-specific array syntax
22299                match self.config.dialect {
22300                    Some(DialectType::PostgreSQL)
22301                    | Some(DialectType::Redshift)
22302                    | Some(DialectType::DuckDB) => {
22303                        // PostgreSQL uses TYPE[] or TYPE[N] syntax
22304                        self.generate_data_type(element_type)?;
22305                        if let Some(dim) = dimension {
22306                            self.write(&format!("[{}]", dim));
22307                        } else {
22308                            self.write("[]");
22309                        }
22310                    }
22311                    Some(DialectType::BigQuery) => {
22312                        self.write_keyword("ARRAY<");
22313                        self.generate_data_type(element_type)?;
22314                        self.write(">");
22315                    }
22316                    Some(DialectType::Snowflake)
22317                    | Some(DialectType::Presto)
22318                    | Some(DialectType::Trino)
22319                    | Some(DialectType::ClickHouse) => {
22320                        // These dialects use Array(TYPE) parentheses syntax
22321                        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
22322                            self.write("Array(");
22323                        } else {
22324                            self.write_keyword("ARRAY(");
22325                        }
22326                        self.generate_data_type(element_type)?;
22327                        self.write(")");
22328                    }
22329                    Some(DialectType::TSQL)
22330                    | Some(DialectType::MySQL)
22331                    | Some(DialectType::Oracle) => {
22332                        // These dialects don't have native array types
22333                        // Fall back to JSON or use native workarounds
22334                        match self.config.dialect {
22335                            Some(DialectType::MySQL) => self.write_keyword("JSON"),
22336                            Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
22337                            _ => self.write_keyword("JSON"),
22338                        }
22339                    }
22340                    _ => {
22341                        // Default: use angle bracket syntax (ARRAY<T>)
22342                        self.write_keyword("ARRAY<");
22343                        self.generate_data_type(element_type)?;
22344                        self.write(">");
22345                    }
22346                }
22347            }
22348            DataType::List { element_type } => {
22349                // Materialize: element_type LIST (postfix syntax)
22350                self.generate_data_type(element_type)?;
22351                self.write_keyword(" LIST");
22352            }
22353            DataType::Map {
22354                key_type,
22355                value_type,
22356            } => {
22357                // Use parentheses for Snowflake and RisingWave, bracket syntax for Materialize, angle brackets for others
22358                match self.config.dialect {
22359                    Some(DialectType::Materialize) => {
22360                        // Materialize: MAP[key_type => value_type]
22361                        self.write_keyword("MAP[");
22362                        self.generate_data_type(key_type)?;
22363                        self.write(" => ");
22364                        self.generate_data_type(value_type)?;
22365                        self.write("]");
22366                    }
22367                    Some(DialectType::Snowflake)
22368                    | Some(DialectType::RisingWave)
22369                    | Some(DialectType::DuckDB)
22370                    | Some(DialectType::Presto)
22371                    | Some(DialectType::Trino)
22372                    | Some(DialectType::Athena) => {
22373                        self.write_keyword("MAP(");
22374                        self.generate_data_type(key_type)?;
22375                        self.write(", ");
22376                        self.generate_data_type(value_type)?;
22377                        self.write(")");
22378                    }
22379                    Some(DialectType::ClickHouse) => {
22380                        // ClickHouse: Map(key_type, value_type) with parenthesized syntax
22381                        // Key types must NOT be wrapped in Nullable
22382                        self.write("Map(");
22383                        self.clickhouse_nullable_depth = -1; // suppress Nullable for key
22384                        self.generate_data_type(key_type)?;
22385                        self.clickhouse_nullable_depth = 0;
22386                        self.write(", ");
22387                        self.generate_data_type(value_type)?;
22388                        self.write(")");
22389                    }
22390                    _ => {
22391                        self.write_keyword("MAP<");
22392                        self.generate_data_type(key_type)?;
22393                        self.write(", ");
22394                        self.generate_data_type(value_type)?;
22395                        self.write(">");
22396                    }
22397                }
22398            }
22399            DataType::Vector {
22400                element_type,
22401                dimension,
22402            } => {
22403                if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
22404                    // SingleStore format: VECTOR(dimension, type_alias)
22405                    self.write_keyword("VECTOR(");
22406                    if let Some(dim) = dimension {
22407                        self.write(&dim.to_string());
22408                    }
22409                    // Map type back to SingleStore alias
22410                    let type_alias = element_type.as_ref().and_then(|et| match et.as_ref() {
22411                        DataType::TinyInt { .. } => Some("I8"),
22412                        DataType::SmallInt { .. } => Some("I16"),
22413                        DataType::Int { .. } => Some("I32"),
22414                        DataType::BigInt { .. } => Some("I64"),
22415                        DataType::Float { .. } => Some("F32"),
22416                        DataType::Double { .. } => Some("F64"),
22417                        _ => None,
22418                    });
22419                    if let Some(alias) = type_alias {
22420                        if dimension.is_some() {
22421                            self.write(", ");
22422                        }
22423                        self.write(alias);
22424                    }
22425                    self.write(")");
22426                } else {
22427                    // Snowflake format: VECTOR(type, dimension)
22428                    self.write_keyword("VECTOR(");
22429                    if let Some(ref et) = element_type {
22430                        self.generate_data_type(et)?;
22431                        if dimension.is_some() {
22432                            self.write(", ");
22433                        }
22434                    }
22435                    if let Some(dim) = dimension {
22436                        self.write(&dim.to_string());
22437                    }
22438                    self.write(")");
22439                }
22440            }
22441            DataType::Object { fields, modifier } => {
22442                self.write_keyword("OBJECT(");
22443                for (i, (name, dt, not_null)) in fields.iter().enumerate() {
22444                    if i > 0 {
22445                        self.write(", ");
22446                    }
22447                    self.write(name);
22448                    self.write(" ");
22449                    self.generate_data_type(dt)?;
22450                    if *not_null {
22451                        self.write_keyword(" NOT NULL");
22452                    }
22453                }
22454                self.write(")");
22455                if let Some(mod_str) = modifier {
22456                    self.write(" ");
22457                    self.write_keyword(mod_str);
22458                }
22459            }
22460            DataType::Struct { fields, nested } => {
22461                // Dialect-specific struct type mappings
22462                match self.config.dialect {
22463                    Some(DialectType::Snowflake) => {
22464                        // Snowflake maps STRUCT to OBJECT
22465                        self.write_keyword("OBJECT(");
22466                        for (i, field) in fields.iter().enumerate() {
22467                            if i > 0 {
22468                                self.write(", ");
22469                            }
22470                            if !field.name.is_empty() {
22471                                self.write(&field.name);
22472                                self.write(" ");
22473                            }
22474                            self.generate_data_type(&field.data_type)?;
22475                        }
22476                        self.write(")");
22477                    }
22478                    Some(DialectType::Presto) | Some(DialectType::Trino) => {
22479                        // Presto/Trino use ROW(name TYPE, ...) syntax
22480                        self.write_keyword("ROW(");
22481                        for (i, field) in fields.iter().enumerate() {
22482                            if i > 0 {
22483                                self.write(", ");
22484                            }
22485                            if !field.name.is_empty() {
22486                                self.write(&field.name);
22487                                self.write(" ");
22488                            }
22489                            self.generate_data_type(&field.data_type)?;
22490                        }
22491                        self.write(")");
22492                    }
22493                    Some(DialectType::DuckDB) => {
22494                        // DuckDB uses parenthesized syntax: STRUCT(name TYPE, ...)
22495                        self.write_keyword("STRUCT(");
22496                        for (i, field) in fields.iter().enumerate() {
22497                            if i > 0 {
22498                                self.write(", ");
22499                            }
22500                            if !field.name.is_empty() {
22501                                self.write(&field.name);
22502                                self.write(" ");
22503                            }
22504                            self.generate_data_type(&field.data_type)?;
22505                        }
22506                        self.write(")");
22507                    }
22508                    Some(DialectType::ClickHouse) => {
22509                        // ClickHouse uses Tuple(name TYPE, ...) for struct types
22510                        self.write("Tuple(");
22511                        for (i, field) in fields.iter().enumerate() {
22512                            if i > 0 {
22513                                self.write(", ");
22514                            }
22515                            if !field.name.is_empty() {
22516                                self.write(&field.name);
22517                                self.write(" ");
22518                            }
22519                            self.generate_data_type(&field.data_type)?;
22520                        }
22521                        self.write(")");
22522                    }
22523                    Some(DialectType::SingleStore) => {
22524                        // SingleStore uses RECORD(name TYPE, ...) for struct types
22525                        self.write_keyword("RECORD(");
22526                        for (i, field) in fields.iter().enumerate() {
22527                            if i > 0 {
22528                                self.write(", ");
22529                            }
22530                            if !field.name.is_empty() {
22531                                self.write(&field.name);
22532                                self.write(" ");
22533                            }
22534                            self.generate_data_type(&field.data_type)?;
22535                        }
22536                        self.write(")");
22537                    }
22538                    _ => {
22539                        // Hive/Spark always use angle bracket syntax: STRUCT<name: TYPE>
22540                        let force_angle_brackets = matches!(
22541                            self.config.dialect,
22542                            Some(DialectType::Hive)
22543                                | Some(DialectType::Spark)
22544                                | Some(DialectType::Databricks)
22545                        );
22546                        if *nested && !force_angle_brackets {
22547                            self.write_keyword("STRUCT(");
22548                            for (i, field) in fields.iter().enumerate() {
22549                                if i > 0 {
22550                                    self.write(", ");
22551                                }
22552                                if !field.name.is_empty() {
22553                                    self.write(&field.name);
22554                                    self.write(" ");
22555                                }
22556                                self.generate_data_type(&field.data_type)?;
22557                            }
22558                            self.write(")");
22559                        } else {
22560                            self.write_keyword("STRUCT<");
22561                            for (i, field) in fields.iter().enumerate() {
22562                                if i > 0 {
22563                                    self.write(", ");
22564                                }
22565                                if !field.name.is_empty() {
22566                                    // Named field: name TYPE (with configurable separator for Hive)
22567                                    self.write(&field.name);
22568                                    self.write(self.config.struct_field_sep);
22569                                }
22570                                // For anonymous fields, just output the type
22571                                self.generate_data_type(&field.data_type)?;
22572                                // Spark/Databricks: Output COMMENT clause if present
22573                                if let Some(comment) = &field.comment {
22574                                    self.write(" COMMENT '");
22575                                    self.write(comment);
22576                                    self.write("'");
22577                                }
22578                                // BigQuery: Output OPTIONS clause if present
22579                                if !field.options.is_empty() {
22580                                    self.write(" ");
22581                                    self.generate_options_clause(&field.options)?;
22582                                }
22583                            }
22584                            self.write(">");
22585                        }
22586                    }
22587                }
22588            }
22589            DataType::Enum {
22590                values,
22591                assignments,
22592            } => {
22593                // DuckDB ENUM type: ENUM('RED', 'GREEN', 'BLUE')
22594                // ClickHouse: Enum('hello' = 1, 'world' = 2)
22595                if self.config.dialect == Some(DialectType::ClickHouse) {
22596                    self.write("Enum(");
22597                } else {
22598                    self.write_keyword("ENUM(");
22599                }
22600                for (i, val) in values.iter().enumerate() {
22601                    if i > 0 {
22602                        self.write(", ");
22603                    }
22604                    self.write("'");
22605                    self.write(val);
22606                    self.write("'");
22607                    if let Some(Some(assignment)) = assignments.get(i) {
22608                        self.write(" = ");
22609                        self.write(assignment);
22610                    }
22611                }
22612                self.write(")");
22613            }
22614            DataType::Set { values } => {
22615                // MySQL SET type: SET('a', 'b', 'c')
22616                self.write_keyword("SET(");
22617                for (i, val) in values.iter().enumerate() {
22618                    if i > 0 {
22619                        self.write(", ");
22620                    }
22621                    self.write("'");
22622                    self.write(val);
22623                    self.write("'");
22624                }
22625                self.write(")");
22626            }
22627            DataType::Union { fields } => {
22628                // DuckDB UNION type: UNION(num INT, str TEXT)
22629                self.write_keyword("UNION(");
22630                for (i, (name, dt)) in fields.iter().enumerate() {
22631                    if i > 0 {
22632                        self.write(", ");
22633                    }
22634                    if !name.is_empty() {
22635                        self.write(name);
22636                        self.write(" ");
22637                    }
22638                    self.generate_data_type(dt)?;
22639                }
22640                self.write(")");
22641            }
22642            DataType::Nullable { inner } => {
22643                // ClickHouse: Nullable(T), other dialects: just the inner type
22644                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
22645                    self.write("Nullable(");
22646                    // Suppress inner Nullable wrapping to prevent Nullable(Nullable(...))
22647                    let saved_depth = self.clickhouse_nullable_depth;
22648                    self.clickhouse_nullable_depth = -1;
22649                    self.generate_data_type(inner)?;
22650                    self.clickhouse_nullable_depth = saved_depth;
22651                    self.write(")");
22652                } else {
22653                    // Map ClickHouse-specific custom type names to standard types
22654                    match inner.as_ref() {
22655                        DataType::Custom { name } if name.to_uppercase() == "DATETIME" => {
22656                            self.generate_data_type(&DataType::Timestamp {
22657                                precision: None,
22658                                timezone: false,
22659                            })?;
22660                        }
22661                        _ => {
22662                            self.generate_data_type(inner)?;
22663                        }
22664                    }
22665                }
22666            }
22667            DataType::Custom { name } => {
22668                // Handle dialect-specific type transformations
22669                let name_upper = name.to_uppercase();
22670                match self.config.dialect {
22671                    Some(DialectType::ClickHouse) => {
22672                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
22673                            (name_upper[..idx].to_string(), &name[idx..])
22674                        } else {
22675                            (name_upper.clone(), "")
22676                        };
22677                        let mapped = match base_upper.as_str() {
22678                            "DATETIME" | "TIMESTAMPTZ" | "TIMESTAMP" | "TIMESTAMPNTZ"
22679                            | "SMALLDATETIME" | "DATETIME2" => "DateTime",
22680                            "DATETIME64" => "DateTime64",
22681                            "DATE32" => "Date32",
22682                            "INT" => "Int32",
22683                            "MEDIUMINT" => "Int32",
22684                            "INT8" => "Int8",
22685                            "INT16" => "Int16",
22686                            "INT32" => "Int32",
22687                            "INT64" => "Int64",
22688                            "INT128" => "Int128",
22689                            "INT256" => "Int256",
22690                            "UINT8" => "UInt8",
22691                            "UINT16" => "UInt16",
22692                            "UINT32" => "UInt32",
22693                            "UINT64" => "UInt64",
22694                            "UINT128" => "UInt128",
22695                            "UINT256" => "UInt256",
22696                            "FLOAT32" => "Float32",
22697                            "FLOAT64" => "Float64",
22698                            "DECIMAL32" => "Decimal32",
22699                            "DECIMAL64" => "Decimal64",
22700                            "DECIMAL128" => "Decimal128",
22701                            "DECIMAL256" => "Decimal256",
22702                            "ENUM" => "Enum",
22703                            "ENUM8" => "Enum8",
22704                            "ENUM16" => "Enum16",
22705                            "FIXEDSTRING" => "FixedString",
22706                            "NESTED" => "Nested",
22707                            "LOWCARDINALITY" => "LowCardinality",
22708                            "NULLABLE" => "Nullable",
22709                            "IPV4" => "IPv4",
22710                            "IPV6" => "IPv6",
22711                            "POINT" => "Point",
22712                            "RING" => "Ring",
22713                            "LINESTRING" => "LineString",
22714                            "MULTILINESTRING" => "MultiLineString",
22715                            "POLYGON" => "Polygon",
22716                            "MULTIPOLYGON" => "MultiPolygon",
22717                            "AGGREGATEFUNCTION" => "AggregateFunction",
22718                            "SIMPLEAGGREGATEFUNCTION" => "SimpleAggregateFunction",
22719                            "DYNAMIC" => "Dynamic",
22720                            _ => "",
22721                        };
22722                        if mapped.is_empty() {
22723                            self.write(name);
22724                        } else {
22725                            self.write(mapped);
22726                            self.write(suffix);
22727                        }
22728                    }
22729                    Some(DialectType::MySQL)
22730                        if name_upper == "TIMESTAMPTZ" || name_upper == "TIMESTAMPLTZ" =>
22731                    {
22732                        // MySQL doesn't support TIMESTAMPTZ/TIMESTAMPLTZ, use TIMESTAMP
22733                        self.write_keyword("TIMESTAMP");
22734                    }
22735                    Some(DialectType::TSQL) if name_upper == "VARIANT" => {
22736                        self.write_keyword("SQL_VARIANT");
22737                    }
22738                    Some(DialectType::DuckDB) if name_upper == "DECFLOAT" => {
22739                        self.write_keyword("DECIMAL(38, 5)");
22740                    }
22741                    Some(DialectType::Exasol) => {
22742                        // Exasol type mappings for custom types
22743                        match name_upper.as_str() {
22744                            // Binary types → VARCHAR
22745                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => self.write_keyword("VARCHAR"),
22746                            // Text types → VARCHAR (TEXT → LONG VARCHAR is handled by DataType::Text)
22747                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => self.write_keyword("VARCHAR"),
22748                            // Integer types
22749                            "MEDIUMINT" => self.write_keyword("INT"),
22750                            // Decimal types → DECIMAL
22751                            "DECIMAL32" | "DECIMAL64" | "DECIMAL128" | "DECIMAL256" => {
22752                                self.write_keyword("DECIMAL")
22753                            }
22754                            // Timestamp types
22755                            "DATETIME" => self.write_keyword("TIMESTAMP"),
22756                            "TIMESTAMPLTZ" => self.write_keyword("TIMESTAMP WITH LOCAL TIME ZONE"),
22757                            _ => self.write(name),
22758                        }
22759                    }
22760                    Some(DialectType::Dremio) => {
22761                        // Dremio type mappings for custom types
22762                        match name_upper.as_str() {
22763                            "TIMESTAMPNTZ" | "DATETIME" => self.write_keyword("TIMESTAMP"),
22764                            "ARRAY" => self.write_keyword("LIST"),
22765                            "NCHAR" => self.write_keyword("VARCHAR"),
22766                            _ => self.write(name),
22767                        }
22768                    }
22769                    // Map dialect-specific custom types to standard SQL types for other dialects
22770                    _ => {
22771                        // Extract base name and args for types with parenthesized args (e.g., DATETIME2(3))
22772                        let (base_upper, _args_str) = if let Some(idx) = name_upper.find('(') {
22773                            (name_upper[..idx].to_string(), Some(&name[idx..]))
22774                        } else {
22775                            (name_upper.clone(), None)
22776                        };
22777
22778                        match base_upper.as_str() {
22779                            "INT64"
22780                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
22781                            {
22782                                self.write_keyword("BIGINT");
22783                            }
22784                            "FLOAT64"
22785                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
22786                            {
22787                                self.write_keyword("DOUBLE");
22788                            }
22789                            "BOOL"
22790                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
22791                            {
22792                                self.write_keyword("BOOLEAN");
22793                            }
22794                            "BYTES"
22795                                if matches!(
22796                                    self.config.dialect,
22797                                    Some(DialectType::Spark)
22798                                        | Some(DialectType::Hive)
22799                                        | Some(DialectType::Databricks)
22800                                ) =>
22801                            {
22802                                self.write_keyword("BINARY");
22803                            }
22804                            "BYTES"
22805                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
22806                            {
22807                                self.write_keyword("VARBINARY");
22808                            }
22809                            // TSQL DATETIME2/SMALLDATETIME -> TIMESTAMP
22810                            "DATETIME2" | "SMALLDATETIME"
22811                                if !matches!(
22812                                    self.config.dialect,
22813                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
22814                                ) =>
22815                            {
22816                                // PostgreSQL preserves precision, others don't
22817                                if matches!(
22818                                    self.config.dialect,
22819                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
22820                                ) {
22821                                    self.write_keyword("TIMESTAMP");
22822                                    if let Some(args) = _args_str {
22823                                        self.write(args);
22824                                    }
22825                                } else {
22826                                    self.write_keyword("TIMESTAMP");
22827                                }
22828                            }
22829                            // TSQL DATETIMEOFFSET -> TIMESTAMPTZ
22830                            "DATETIMEOFFSET"
22831                                if !matches!(
22832                                    self.config.dialect,
22833                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
22834                                ) =>
22835                            {
22836                                if matches!(
22837                                    self.config.dialect,
22838                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
22839                                ) {
22840                                    self.write_keyword("TIMESTAMPTZ");
22841                                    if let Some(args) = _args_str {
22842                                        self.write(args);
22843                                    }
22844                                } else {
22845                                    self.write_keyword("TIMESTAMPTZ");
22846                                }
22847                            }
22848                            // TSQL UNIQUEIDENTIFIER -> UUID or STRING
22849                            "UNIQUEIDENTIFIER"
22850                                if !matches!(
22851                                    self.config.dialect,
22852                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
22853                                ) =>
22854                            {
22855                                match self.config.dialect {
22856                                    Some(DialectType::Spark)
22857                                    | Some(DialectType::Databricks)
22858                                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
22859                                    _ => self.write_keyword("UUID"),
22860                                }
22861                            }
22862                            // TSQL BIT -> BOOLEAN for most dialects
22863                            "BIT"
22864                                if !matches!(
22865                                    self.config.dialect,
22866                                    Some(DialectType::TSQL)
22867                                        | Some(DialectType::Fabric)
22868                                        | Some(DialectType::PostgreSQL)
22869                                        | Some(DialectType::MySQL)
22870                                        | Some(DialectType::DuckDB)
22871                                ) =>
22872                            {
22873                                self.write_keyword("BOOLEAN");
22874                            }
22875                            // TSQL NVARCHAR -> VARCHAR (with default size 30 for some dialects)
22876                            "NVARCHAR"
22877                                if !matches!(
22878                                    self.config.dialect,
22879                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
22880                                ) =>
22881                            {
22882                                match self.config.dialect {
22883                                    Some(DialectType::Oracle) => {
22884                                        // Oracle: NVARCHAR -> NVARCHAR2
22885                                        self.write_keyword("NVARCHAR2");
22886                                        if let Some(args) = _args_str {
22887                                            self.write(args);
22888                                        }
22889                                    }
22890                                    Some(DialectType::BigQuery) => {
22891                                        // BigQuery: NVARCHAR -> STRING
22892                                        self.write_keyword("STRING");
22893                                    }
22894                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
22895                                        self.write_keyword("TEXT");
22896                                        if let Some(args) = _args_str {
22897                                            self.write(args);
22898                                        }
22899                                    }
22900                                    Some(DialectType::Hive) => {
22901                                        // Hive: NVARCHAR -> STRING
22902                                        self.write_keyword("STRING");
22903                                    }
22904                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
22905                                        if _args_str.is_some() {
22906                                            self.write_keyword("VARCHAR");
22907                                            self.write(_args_str.unwrap());
22908                                        } else {
22909                                            self.write_keyword("STRING");
22910                                        }
22911                                    }
22912                                    _ => {
22913                                        self.write_keyword("VARCHAR");
22914                                        if let Some(args) = _args_str {
22915                                            self.write(args);
22916                                        }
22917                                    }
22918                                }
22919                            }
22920                            // NCHAR -> CHAR (NCHAR for Oracle/TSQL, STRING for BigQuery/Hive)
22921                            "NCHAR"
22922                                if !matches!(
22923                                    self.config.dialect,
22924                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
22925                                ) =>
22926                            {
22927                                match self.config.dialect {
22928                                    Some(DialectType::Oracle) => {
22929                                        // Oracle natively supports NCHAR
22930                                        self.write_keyword("NCHAR");
22931                                        if let Some(args) = _args_str {
22932                                            self.write(args);
22933                                        }
22934                                    }
22935                                    Some(DialectType::BigQuery) => {
22936                                        // BigQuery: NCHAR -> STRING
22937                                        self.write_keyword("STRING");
22938                                    }
22939                                    Some(DialectType::Hive) => {
22940                                        // Hive: NCHAR -> STRING
22941                                        self.write_keyword("STRING");
22942                                    }
22943                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
22944                                        self.write_keyword("TEXT");
22945                                        if let Some(args) = _args_str {
22946                                            self.write(args);
22947                                        }
22948                                    }
22949                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
22950                                        if _args_str.is_some() {
22951                                            self.write_keyword("CHAR");
22952                                            self.write(_args_str.unwrap());
22953                                        } else {
22954                                            self.write_keyword("STRING");
22955                                        }
22956                                    }
22957                                    _ => {
22958                                        self.write_keyword("CHAR");
22959                                        if let Some(args) = _args_str {
22960                                            self.write(args);
22961                                        }
22962                                    }
22963                                }
22964                            }
22965                            // MySQL text variant types -> map to appropriate target type
22966                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
22967                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => match self.config.dialect {
22968                                Some(DialectType::MySQL)
22969                                | Some(DialectType::SingleStore)
22970                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
22971                                Some(DialectType::Spark)
22972                                | Some(DialectType::Databricks)
22973                                | Some(DialectType::Hive) => self.write_keyword("TEXT"),
22974                                Some(DialectType::BigQuery) => self.write_keyword("STRING"),
22975                                Some(DialectType::Presto)
22976                                | Some(DialectType::Trino)
22977                                | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
22978                                Some(DialectType::Snowflake)
22979                                | Some(DialectType::Redshift)
22980                                | Some(DialectType::Dremio) => self.write_keyword("VARCHAR"),
22981                                _ => self.write_keyword("TEXT"),
22982                            },
22983                            // MySQL blob variant types -> map to appropriate target type
22984                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
22985                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => match self.config.dialect {
22986                                Some(DialectType::MySQL)
22987                                | Some(DialectType::SingleStore)
22988                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
22989                                Some(DialectType::Spark)
22990                                | Some(DialectType::Databricks)
22991                                | Some(DialectType::Hive) => self.write_keyword("BLOB"),
22992                                Some(DialectType::DuckDB) => self.write_keyword("VARBINARY"),
22993                                Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
22994                                Some(DialectType::Presto)
22995                                | Some(DialectType::Trino)
22996                                | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
22997                                Some(DialectType::Snowflake)
22998                                | Some(DialectType::Redshift)
22999                                | Some(DialectType::Dremio) => self.write_keyword("VARBINARY"),
23000                                _ => self.write_keyword("BLOB"),
23001                            },
23002                            // LONGVARCHAR -> TEXT for SQLite, VARCHAR for others
23003                            "LONGVARCHAR" => match self.config.dialect {
23004                                Some(DialectType::SQLite) => self.write_keyword("TEXT"),
23005                                _ => self.write_keyword("VARCHAR"),
23006                            },
23007                            // DATETIME -> TIMESTAMP for most, DATETIME for MySQL/Doris/StarRocks/Snowflake
23008                            "DATETIME" => {
23009                                match self.config.dialect {
23010                                    Some(DialectType::MySQL)
23011                                    | Some(DialectType::Doris)
23012                                    | Some(DialectType::StarRocks)
23013                                    | Some(DialectType::TSQL)
23014                                    | Some(DialectType::Fabric)
23015                                    | Some(DialectType::BigQuery)
23016                                    | Some(DialectType::SQLite)
23017                                    | Some(DialectType::Snowflake) => {
23018                                        self.write_keyword("DATETIME");
23019                                        if let Some(args) = _args_str {
23020                                            self.write(args);
23021                                        }
23022                                    }
23023                                    Some(_) => {
23024                                        // Only map to TIMESTAMP when we have a specific target dialect
23025                                        self.write_keyword("TIMESTAMP");
23026                                        if let Some(args) = _args_str {
23027                                            self.write(args);
23028                                        }
23029                                    }
23030                                    None => {
23031                                        // No dialect - preserve original
23032                                        self.write(name);
23033                                    }
23034                                }
23035                            }
23036                            // VARCHAR2/NVARCHAR2 (Oracle) -> VARCHAR for non-Oracle targets
23037                            "VARCHAR2"
23038                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
23039                            {
23040                                match self.config.dialect {
23041                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
23042                                        self.write_keyword("TEXT");
23043                                    }
23044                                    Some(DialectType::Hive)
23045                                    | Some(DialectType::Spark)
23046                                    | Some(DialectType::Databricks)
23047                                    | Some(DialectType::BigQuery)
23048                                    | Some(DialectType::ClickHouse)
23049                                    | Some(DialectType::StarRocks)
23050                                    | Some(DialectType::Doris) => {
23051                                        self.write_keyword("STRING");
23052                                    }
23053                                    _ => {
23054                                        self.write_keyword("VARCHAR");
23055                                        if let Some(args) = _args_str {
23056                                            self.write(args);
23057                                        }
23058                                    }
23059                                }
23060                            }
23061                            "NVARCHAR2"
23062                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
23063                            {
23064                                match self.config.dialect {
23065                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
23066                                        self.write_keyword("TEXT");
23067                                    }
23068                                    Some(DialectType::Hive)
23069                                    | Some(DialectType::Spark)
23070                                    | Some(DialectType::Databricks)
23071                                    | Some(DialectType::BigQuery)
23072                                    | Some(DialectType::ClickHouse)
23073                                    | Some(DialectType::StarRocks)
23074                                    | Some(DialectType::Doris) => {
23075                                        self.write_keyword("STRING");
23076                                    }
23077                                    _ => {
23078                                        self.write_keyword("VARCHAR");
23079                                        if let Some(args) = _args_str {
23080                                            self.write(args);
23081                                        }
23082                                    }
23083                                }
23084                            }
23085                            _ => self.write(name),
23086                        }
23087                    }
23088                }
23089            }
23090            DataType::Geometry { subtype, srid } => {
23091                // Dialect-specific geometry type mappings
23092                match self.config.dialect {
23093                    Some(DialectType::MySQL) => {
23094                        // MySQL uses POINT SRID 4326 syntax for specific types
23095                        if let Some(sub) = subtype {
23096                            self.write_keyword(sub);
23097                            if let Some(s) = srid {
23098                                self.write(" SRID ");
23099                                self.write(&s.to_string());
23100                            }
23101                        } else {
23102                            self.write_keyword("GEOMETRY");
23103                        }
23104                    }
23105                    Some(DialectType::BigQuery) => {
23106                        // BigQuery only supports GEOGRAPHY, not GEOMETRY
23107                        self.write_keyword("GEOGRAPHY");
23108                    }
23109                    Some(DialectType::Teradata) => {
23110                        // Teradata uses ST_GEOMETRY
23111                        self.write_keyword("ST_GEOMETRY");
23112                        if subtype.is_some() || srid.is_some() {
23113                            self.write("(");
23114                            if let Some(sub) = subtype {
23115                                self.write_keyword(sub);
23116                            }
23117                            if let Some(s) = srid {
23118                                if subtype.is_some() {
23119                                    self.write(", ");
23120                                }
23121                                self.write(&s.to_string());
23122                            }
23123                            self.write(")");
23124                        }
23125                    }
23126                    _ => {
23127                        // PostgreSQL, Snowflake, DuckDB use GEOMETRY(subtype, srid) syntax
23128                        self.write_keyword("GEOMETRY");
23129                        if subtype.is_some() || srid.is_some() {
23130                            self.write("(");
23131                            if let Some(sub) = subtype {
23132                                self.write_keyword(sub);
23133                            }
23134                            if let Some(s) = srid {
23135                                if subtype.is_some() {
23136                                    self.write(", ");
23137                                }
23138                                self.write(&s.to_string());
23139                            }
23140                            self.write(")");
23141                        }
23142                    }
23143                }
23144            }
23145            DataType::Geography { subtype, srid } => {
23146                // Dialect-specific geography type mappings
23147                match self.config.dialect {
23148                    Some(DialectType::MySQL) => {
23149                        // MySQL doesn't have native GEOGRAPHY, use GEOMETRY with SRID 4326
23150                        if let Some(sub) = subtype {
23151                            self.write_keyword(sub);
23152                        } else {
23153                            self.write_keyword("GEOMETRY");
23154                        }
23155                        // Geography implies SRID 4326 (WGS84)
23156                        let effective_srid = srid.unwrap_or(4326);
23157                        self.write(" SRID ");
23158                        self.write(&effective_srid.to_string());
23159                    }
23160                    Some(DialectType::BigQuery) => {
23161                        // BigQuery uses simple GEOGRAPHY without parameters
23162                        self.write_keyword("GEOGRAPHY");
23163                    }
23164                    Some(DialectType::Snowflake) => {
23165                        // Snowflake uses GEOGRAPHY without parameters
23166                        self.write_keyword("GEOGRAPHY");
23167                    }
23168                    _ => {
23169                        // PostgreSQL uses GEOGRAPHY(subtype, srid) syntax
23170                        self.write_keyword("GEOGRAPHY");
23171                        if subtype.is_some() || srid.is_some() {
23172                            self.write("(");
23173                            if let Some(sub) = subtype {
23174                                self.write_keyword(sub);
23175                            }
23176                            if let Some(s) = srid {
23177                                if subtype.is_some() {
23178                                    self.write(", ");
23179                                }
23180                                self.write(&s.to_string());
23181                            }
23182                            self.write(")");
23183                        }
23184                    }
23185                }
23186            }
23187            DataType::CharacterSet { name } => {
23188                // For MySQL CONVERT USING - output as CHAR CHARACTER SET name
23189                self.write_keyword("CHAR CHARACTER SET ");
23190                self.write(name);
23191            }
23192            _ => self.write("UNKNOWN"),
23193        }
23194        Ok(())
23195    }
23196
23197    // === Helper methods ===
23198
23199    fn write(&mut self, s: &str) {
23200        self.output.push_str(s);
23201    }
23202
23203    fn write_space(&mut self) {
23204        self.output.push(' ');
23205    }
23206
23207    fn write_keyword(&mut self, keyword: &str) {
23208        if self.config.uppercase_keywords {
23209            self.output.push_str(keyword);
23210        } else {
23211            self.output.push_str(&keyword.to_lowercase());
23212        }
23213    }
23214
23215    /// Write a function name respecting the normalize_functions config setting
23216    fn write_func_name(&mut self, name: &str) {
23217        let normalized = self.normalize_func_name(name);
23218        self.output.push_str(&normalized);
23219    }
23220
23221    /// Convert strptime format string to Exasol format string
23222    /// Exasol TIME_MAPPING (reverse of Python sqlglot):
23223    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH, %M -> MI, %S -> SS, %a -> DY
23224    fn convert_strptime_to_exasol_format(format: &str) -> String {
23225        let mut result = String::new();
23226        let chars: Vec<char> = format.chars().collect();
23227        let mut i = 0;
23228        while i < chars.len() {
23229            if chars[i] == '%' && i + 1 < chars.len() {
23230                let spec = chars[i + 1];
23231                let exasol_spec = match spec {
23232                    'Y' => "YYYY",
23233                    'y' => "YY",
23234                    'm' => "MM",
23235                    'd' => "DD",
23236                    'H' => "HH",
23237                    'M' => "MI",
23238                    'S' => "SS",
23239                    'a' => "DY",    // abbreviated weekday name
23240                    'A' => "DAY",   // full weekday name
23241                    'b' => "MON",   // abbreviated month name
23242                    'B' => "MONTH", // full month name
23243                    'I' => "H12",   // 12-hour format
23244                    'u' => "ID",    // ISO weekday (1-7)
23245                    'V' => "IW",    // ISO week number
23246                    'G' => "IYYY",  // ISO year
23247                    'W' => "UW",    // Week number (Monday as first day)
23248                    'U' => "UW",    // Week number (Sunday as first day)
23249                    'z' => "Z",     // timezone offset
23250                    _ => {
23251                        // Unknown specifier, keep as-is
23252                        result.push('%');
23253                        result.push(spec);
23254                        i += 2;
23255                        continue;
23256                    }
23257                };
23258                result.push_str(exasol_spec);
23259                i += 2;
23260            } else {
23261                result.push(chars[i]);
23262                i += 1;
23263            }
23264        }
23265        result
23266    }
23267
23268    /// Convert strptime format string to PostgreSQL/Redshift format string
23269    /// PostgreSQL INVERSE_TIME_MAPPING from Python sqlglot:
23270    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH24, %M -> MI, %S -> SS, %f -> US, etc.
23271    fn convert_strptime_to_postgres_format(format: &str) -> String {
23272        let mut result = String::new();
23273        let chars: Vec<char> = format.chars().collect();
23274        let mut i = 0;
23275        while i < chars.len() {
23276            if chars[i] == '%' && i + 1 < chars.len() {
23277                // Check for %-d, %-m, etc. (non-padded, 3-char sequence)
23278                if chars[i + 1] == '-' && i + 2 < chars.len() {
23279                    let spec = chars[i + 2];
23280                    let pg_spec = match spec {
23281                        'd' => "FMDD",
23282                        'm' => "FMMM",
23283                        'H' => "FMHH24",
23284                        'M' => "FMMI",
23285                        'S' => "FMSS",
23286                        _ => {
23287                            result.push('%');
23288                            result.push('-');
23289                            result.push(spec);
23290                            i += 3;
23291                            continue;
23292                        }
23293                    };
23294                    result.push_str(pg_spec);
23295                    i += 3;
23296                    continue;
23297                }
23298                let spec = chars[i + 1];
23299                let pg_spec = match spec {
23300                    'Y' => "YYYY",
23301                    'y' => "YY",
23302                    'm' => "MM",
23303                    'd' => "DD",
23304                    'H' => "HH24",
23305                    'I' => "HH12",
23306                    'M' => "MI",
23307                    'S' => "SS",
23308                    'f' => "US",      // microseconds
23309                    'u' => "D",       // day of week (1=Monday)
23310                    'j' => "DDD",     // day of year
23311                    'z' => "OF",      // UTC offset
23312                    'Z' => "TZ",      // timezone name
23313                    'A' => "TMDay",   // full weekday name
23314                    'a' => "TMDy",    // abbreviated weekday name
23315                    'b' => "TMMon",   // abbreviated month name
23316                    'B' => "TMMonth", // full month name
23317                    'U' => "WW",      // week number
23318                    _ => {
23319                        // Unknown specifier, keep as-is
23320                        result.push('%');
23321                        result.push(spec);
23322                        i += 2;
23323                        continue;
23324                    }
23325                };
23326                result.push_str(pg_spec);
23327                i += 2;
23328            } else {
23329                result.push(chars[i]);
23330                i += 1;
23331            }
23332        }
23333        result
23334    }
23335
23336    /// Write a LIMIT expression value, evaluating constant expressions if limit_only_literals is set
23337    fn write_limit_expr(&mut self, expr: &Expression) -> Result<()> {
23338        if self.config.limit_only_literals {
23339            if let Some(value) = Self::try_evaluate_constant(expr) {
23340                self.write(&value.to_string());
23341                return Ok(());
23342            }
23343        }
23344        self.generate_expression(expr)
23345    }
23346
23347    /// Format a comment with proper spacing.
23348    /// Converts `/*text*/` to `/* text */` (adding internal spaces if not present).
23349    /// Python SQLGlot normalizes comment format to have spaces inside block comments.
23350    fn write_formatted_comment(&mut self, comment: &str) {
23351        // Normalize all comments to block comment format /* ... */
23352        // This matches Python sqlglot behavior which always outputs block comments
23353        let content = if comment.starts_with("/*") && comment.ends_with("*/") {
23354            // Already block comment - extract inner content
23355            // Preserve internal whitespace, but ensure at least one space padding
23356            &comment[2..comment.len() - 2]
23357        } else if comment.starts_with("--") {
23358            // Line comment - extract content after --
23359            // Preserve internal whitespace (e.g., "--       x" -> "/*       x */")
23360            &comment[2..]
23361        } else {
23362            // Raw content (no delimiters)
23363            comment
23364        };
23365        // Skip empty comments (e.g., bare "--" with no content)
23366        if content.trim().is_empty() {
23367            return;
23368        }
23369        // Ensure at least one space after /* and before */
23370        self.output.push_str("/*");
23371        if !content.starts_with(' ') {
23372            self.output.push(' ');
23373        }
23374        self.output.push_str(content);
23375        if !content.ends_with(' ') {
23376            self.output.push(' ');
23377        }
23378        self.output.push_str("*/");
23379    }
23380
23381    /// Escape a raw block content (from dollar-quoted string) for single-quoted output.
23382    /// Escapes single quotes with backslash, and for Snowflake also escapes backslashes.
23383    fn escape_block_for_single_quote(&self, block: &str) -> String {
23384        let escape_backslash = matches!(
23385            self.config.dialect,
23386            Some(crate::dialects::DialectType::Snowflake)
23387        );
23388        let mut escaped = String::with_capacity(block.len() + 4);
23389        for ch in block.chars() {
23390            if ch == '\'' {
23391                escaped.push('\\');
23392                escaped.push('\'');
23393            } else if escape_backslash && ch == '\\' {
23394                escaped.push('\\');
23395                escaped.push('\\');
23396            } else {
23397                escaped.push(ch);
23398            }
23399        }
23400        escaped
23401    }
23402
23403    fn write_newline(&mut self) {
23404        self.output.push('\n');
23405    }
23406
23407    fn write_indent(&mut self) {
23408        for _ in 0..self.indent_level {
23409            self.output.push_str(&self.config.indent);
23410        }
23411    }
23412
23413    // === SQLGlot-style pretty printing helpers ===
23414
23415    /// Returns the separator string for pretty printing.
23416    /// Check if the total length of arguments exceeds max_text_width.
23417    /// Used for dynamic line breaking in expressions() formatting.
23418    fn too_wide(&self, args: &[String]) -> bool {
23419        args.iter().map(|s| s.len()).sum::<usize>() > self.config.max_text_width
23420    }
23421
23422    /// Generate an expression to a string using a temporary non-pretty generator.
23423    /// Useful for width calculations before deciding on formatting.
23424    fn generate_to_string(&self, expr: &Expression) -> Result<String> {
23425        let config = GeneratorConfig {
23426            pretty: false,
23427            dialect: self.config.dialect,
23428            ..Default::default()
23429        };
23430        let mut gen = Generator::with_config(config);
23431        gen.generate_expression(expr)?;
23432        Ok(gen.output)
23433    }
23434
23435    /// Writes a clause with a single condition (WHERE, HAVING, QUALIFY).
23436    /// In pretty mode: newline + indented keyword + newline + indented condition
23437    fn write_clause_condition(&mut self, keyword: &str, condition: &Expression) -> Result<()> {
23438        if self.config.pretty {
23439            self.write_newline();
23440            self.write_indent();
23441            self.write_keyword(keyword);
23442            self.write_newline();
23443            self.indent_level += 1;
23444            self.write_indent();
23445            self.generate_expression(condition)?;
23446            self.indent_level -= 1;
23447        } else {
23448            self.write_space();
23449            self.write_keyword(keyword);
23450            self.write_space();
23451            self.generate_expression(condition)?;
23452        }
23453        Ok(())
23454    }
23455
23456    /// Writes a clause with a list of expressions (GROUP BY, DISTRIBUTE BY, CLUSTER BY).
23457    /// In pretty mode: each expression on new line with indentation
23458    fn write_clause_expressions(&mut self, keyword: &str, exprs: &[Expression]) -> Result<()> {
23459        if exprs.is_empty() {
23460            return Ok(());
23461        }
23462
23463        if self.config.pretty {
23464            self.write_newline();
23465            self.write_indent();
23466            self.write_keyword(keyword);
23467            self.write_newline();
23468            self.indent_level += 1;
23469            for (i, expr) in exprs.iter().enumerate() {
23470                if i > 0 {
23471                    self.write(",");
23472                    self.write_newline();
23473                }
23474                self.write_indent();
23475                self.generate_expression(expr)?;
23476            }
23477            self.indent_level -= 1;
23478        } else {
23479            self.write_space();
23480            self.write_keyword(keyword);
23481            self.write_space();
23482            for (i, expr) in exprs.iter().enumerate() {
23483                if i > 0 {
23484                    self.write(", ");
23485                }
23486                self.generate_expression(expr)?;
23487            }
23488        }
23489        Ok(())
23490    }
23491
23492    /// Writes ORDER BY / SORT BY clause with Ordered expressions
23493    fn write_order_clause(&mut self, keyword: &str, orderings: &[Ordered]) -> Result<()> {
23494        if orderings.is_empty() {
23495            return Ok(());
23496        }
23497
23498        if self.config.pretty {
23499            self.write_newline();
23500            self.write_indent();
23501            self.write_keyword(keyword);
23502            self.write_newline();
23503            self.indent_level += 1;
23504            for (i, ordered) in orderings.iter().enumerate() {
23505                if i > 0 {
23506                    self.write(",");
23507                    self.write_newline();
23508                }
23509                self.write_indent();
23510                self.generate_ordered(ordered)?;
23511            }
23512            self.indent_level -= 1;
23513        } else {
23514            self.write_space();
23515            self.write_keyword(keyword);
23516            self.write_space();
23517            for (i, ordered) in orderings.iter().enumerate() {
23518                if i > 0 {
23519                    self.write(", ");
23520                }
23521                self.generate_ordered(ordered)?;
23522            }
23523        }
23524        Ok(())
23525    }
23526
23527    /// Writes WINDOW clause with named window definitions
23528    fn write_window_clause(&mut self, windows: &[NamedWindow]) -> Result<()> {
23529        if windows.is_empty() {
23530            return Ok(());
23531        }
23532
23533        if self.config.pretty {
23534            self.write_newline();
23535            self.write_indent();
23536            self.write_keyword("WINDOW");
23537            self.write_newline();
23538            self.indent_level += 1;
23539            for (i, named_window) in windows.iter().enumerate() {
23540                if i > 0 {
23541                    self.write(",");
23542                    self.write_newline();
23543                }
23544                self.write_indent();
23545                self.generate_identifier(&named_window.name)?;
23546                self.write_space();
23547                self.write_keyword("AS");
23548                self.write(" (");
23549                self.generate_over(&named_window.spec)?;
23550                self.write(")");
23551            }
23552            self.indent_level -= 1;
23553        } else {
23554            self.write_space();
23555            self.write_keyword("WINDOW");
23556            self.write_space();
23557            for (i, named_window) in windows.iter().enumerate() {
23558                if i > 0 {
23559                    self.write(", ");
23560                }
23561                self.generate_identifier(&named_window.name)?;
23562                self.write_space();
23563                self.write_keyword("AS");
23564                self.write(" (");
23565                self.generate_over(&named_window.spec)?;
23566                self.write(")");
23567            }
23568        }
23569        Ok(())
23570    }
23571
23572    // === BATCH-GENERATED STUB METHODS (481 variants) ===
23573    fn generate_ai_agg(&mut self, e: &AIAgg) -> Result<()> {
23574        // AI_AGG(this, expression)
23575        self.write_keyword("AI_AGG");
23576        self.write("(");
23577        self.generate_expression(&e.this)?;
23578        self.write(", ");
23579        self.generate_expression(&e.expression)?;
23580        self.write(")");
23581        Ok(())
23582    }
23583
23584    fn generate_ai_classify(&mut self, e: &AIClassify) -> Result<()> {
23585        // AI_CLASSIFY(input, [categories], [config])
23586        self.write_keyword("AI_CLASSIFY");
23587        self.write("(");
23588        self.generate_expression(&e.this)?;
23589        if let Some(categories) = &e.categories {
23590            self.write(", ");
23591            self.generate_expression(categories)?;
23592        }
23593        if let Some(config) = &e.config {
23594            self.write(", ");
23595            self.generate_expression(config)?;
23596        }
23597        self.write(")");
23598        Ok(())
23599    }
23600
23601    fn generate_add_partition(&mut self, e: &AddPartition) -> Result<()> {
23602        // Python: return f"ADD {exists}{self.sql(expression.this)}{location}"
23603        self.write_keyword("ADD");
23604        self.write_space();
23605        if e.exists {
23606            self.write_keyword("IF NOT EXISTS");
23607            self.write_space();
23608        }
23609        self.generate_expression(&e.this)?;
23610        if let Some(location) = &e.location {
23611            self.write_space();
23612            self.generate_expression(location)?;
23613        }
23614        Ok(())
23615    }
23616
23617    fn generate_algorithm_property(&mut self, e: &AlgorithmProperty) -> Result<()> {
23618        // Python: return f"ALGORITHM={self.sql(expression, 'this')}"
23619        self.write_keyword("ALGORITHM");
23620        self.write("=");
23621        self.generate_expression(&e.this)?;
23622        Ok(())
23623    }
23624
23625    fn generate_aliases(&mut self, e: &Aliases) -> Result<()> {
23626        // Python: return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
23627        self.generate_expression(&e.this)?;
23628        self.write_space();
23629        self.write_keyword("AS");
23630        self.write(" (");
23631        for (i, expr) in e.expressions.iter().enumerate() {
23632            if i > 0 {
23633                self.write(", ");
23634            }
23635            self.generate_expression(expr)?;
23636        }
23637        self.write(")");
23638        Ok(())
23639    }
23640
23641    fn generate_allowed_values_property(&mut self, e: &AllowedValuesProperty) -> Result<()> {
23642        // Python: return f"ALLOWED_VALUES {self.expressions(e, flat=True)}"
23643        self.write_keyword("ALLOWED_VALUES");
23644        self.write_space();
23645        for (i, expr) in e.expressions.iter().enumerate() {
23646            if i > 0 {
23647                self.write(", ");
23648            }
23649            self.generate_expression(expr)?;
23650        }
23651        Ok(())
23652    }
23653
23654    fn generate_alter_column(&mut self, e: &AlterColumn) -> Result<()> {
23655        // Python: complex logic based on dtype, default, comment, visible, etc.
23656        self.write_keyword("ALTER COLUMN");
23657        self.write_space();
23658        self.generate_expression(&e.this)?;
23659
23660        if let Some(dtype) = &e.dtype {
23661            self.write_space();
23662            self.write_keyword("SET DATA TYPE");
23663            self.write_space();
23664            self.generate_expression(dtype)?;
23665            if let Some(collate) = &e.collate {
23666                self.write_space();
23667                self.write_keyword("COLLATE");
23668                self.write_space();
23669                self.generate_expression(collate)?;
23670            }
23671            if let Some(using) = &e.using {
23672                self.write_space();
23673                self.write_keyword("USING");
23674                self.write_space();
23675                self.generate_expression(using)?;
23676            }
23677        } else if let Some(default) = &e.default {
23678            self.write_space();
23679            self.write_keyword("SET DEFAULT");
23680            self.write_space();
23681            self.generate_expression(default)?;
23682        } else if let Some(comment) = &e.comment {
23683            self.write_space();
23684            self.write_keyword("COMMENT");
23685            self.write_space();
23686            self.generate_expression(comment)?;
23687        } else if let Some(drop) = &e.drop {
23688            self.write_space();
23689            self.write_keyword("DROP");
23690            self.write_space();
23691            self.generate_expression(drop)?;
23692        } else if let Some(visible) = &e.visible {
23693            self.write_space();
23694            self.generate_expression(visible)?;
23695        } else if let Some(rename_to) = &e.rename_to {
23696            self.write_space();
23697            self.write_keyword("RENAME TO");
23698            self.write_space();
23699            self.generate_expression(rename_to)?;
23700        } else if let Some(allow_null) = &e.allow_null {
23701            self.write_space();
23702            self.generate_expression(allow_null)?;
23703        }
23704        Ok(())
23705    }
23706
23707    fn generate_alter_session(&mut self, e: &AlterSession) -> Result<()> {
23708        // Python: keyword = "UNSET" if expression.args.get("unset") else "SET"; return f"{keyword} {items_sql}"
23709        self.write_keyword("ALTER SESSION");
23710        self.write_space();
23711        if e.unset.is_some() {
23712            self.write_keyword("UNSET");
23713        } else {
23714            self.write_keyword("SET");
23715        }
23716        self.write_space();
23717        for (i, expr) in e.expressions.iter().enumerate() {
23718            if i > 0 {
23719                self.write(", ");
23720            }
23721            self.generate_expression(expr)?;
23722        }
23723        Ok(())
23724    }
23725
23726    fn generate_alter_set(&mut self, e: &AlterSet) -> Result<()> {
23727        // Python (Snowflake): return f"SET{exprs}{file_format}{copy_options}{tag}"
23728        self.write_keyword("SET");
23729
23730        // Generate option (e.g., AUTHORIZATION, LOGGED, UNLOGGED, etc.)
23731        if let Some(opt) = &e.option {
23732            self.write_space();
23733            self.generate_expression(opt)?;
23734        }
23735
23736        // Generate PROPERTIES (for Trino SET PROPERTIES x = y, ...)
23737        // Check if expressions look like property assignments
23738        if !e.expressions.is_empty() {
23739            // Check if this looks like property assignments (for SET PROPERTIES)
23740            let is_properties = e
23741                .expressions
23742                .iter()
23743                .any(|expr| matches!(expr, Expression::Eq(_)));
23744            if is_properties && e.option.is_none() {
23745                self.write_space();
23746                self.write_keyword("PROPERTIES");
23747            }
23748            self.write_space();
23749            for (i, expr) in e.expressions.iter().enumerate() {
23750                if i > 0 {
23751                    self.write(", ");
23752                }
23753                self.generate_expression(expr)?;
23754            }
23755        }
23756
23757        // Generate STAGE_FILE_FORMAT = (...) with space-separated properties
23758        if let Some(file_format) = &e.file_format {
23759            self.write(" ");
23760            self.write_keyword("STAGE_FILE_FORMAT");
23761            self.write(" = (");
23762            self.generate_space_separated_properties(file_format)?;
23763            self.write(")");
23764        }
23765
23766        // Generate STAGE_COPY_OPTIONS = (...) with space-separated properties
23767        if let Some(copy_options) = &e.copy_options {
23768            self.write(" ");
23769            self.write_keyword("STAGE_COPY_OPTIONS");
23770            self.write(" = (");
23771            self.generate_space_separated_properties(copy_options)?;
23772            self.write(")");
23773        }
23774
23775        // Generate TAG ...
23776        if let Some(tag) = &e.tag {
23777            self.write(" ");
23778            self.write_keyword("TAG");
23779            self.write(" ");
23780            self.generate_expression(tag)?;
23781        }
23782
23783        Ok(())
23784    }
23785
23786    /// Generate space-separated properties (for Snowflake STAGE_FILE_FORMAT, etc.)
23787    fn generate_space_separated_properties(&mut self, expr: &Expression) -> Result<()> {
23788        match expr {
23789            Expression::Tuple(t) => {
23790                for (i, prop) in t.expressions.iter().enumerate() {
23791                    if i > 0 {
23792                        self.write(" ");
23793                    }
23794                    self.generate_expression(prop)?;
23795                }
23796            }
23797            _ => {
23798                self.generate_expression(expr)?;
23799            }
23800        }
23801        Ok(())
23802    }
23803
23804    fn generate_alter_sort_key(&mut self, e: &AlterSortKey) -> Result<()> {
23805        // Python: return f"ALTER{compound} SORTKEY {this or expressions}"
23806        self.write_keyword("ALTER");
23807        if e.compound.is_some() {
23808            self.write_space();
23809            self.write_keyword("COMPOUND");
23810        }
23811        self.write_space();
23812        self.write_keyword("SORTKEY");
23813        self.write_space();
23814        if let Some(this) = &e.this {
23815            self.generate_expression(this)?;
23816        } else if !e.expressions.is_empty() {
23817            self.write("(");
23818            for (i, expr) in e.expressions.iter().enumerate() {
23819                if i > 0 {
23820                    self.write(", ");
23821                }
23822                self.generate_expression(expr)?;
23823            }
23824            self.write(")");
23825        }
23826        Ok(())
23827    }
23828
23829    fn generate_analyze(&mut self, e: &Analyze) -> Result<()> {
23830        // Python: return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
23831        self.write_keyword("ANALYZE");
23832        if !e.options.is_empty() {
23833            self.write_space();
23834            for (i, opt) in e.options.iter().enumerate() {
23835                if i > 0 {
23836                    self.write_space();
23837                }
23838                // Write options as keywords (not identifiers) to avoid quoting reserved words like FULL
23839                if let Expression::Identifier(id) = opt {
23840                    self.write_keyword(&id.name);
23841                } else {
23842                    self.generate_expression(opt)?;
23843                }
23844            }
23845        }
23846        if let Some(kind) = &e.kind {
23847            self.write_space();
23848            self.write_keyword(kind);
23849        }
23850        if let Some(this) = &e.this {
23851            self.write_space();
23852            self.generate_expression(this)?;
23853        }
23854        // Column list: ANALYZE tbl(col1, col2) (PostgreSQL)
23855        if !e.columns.is_empty() {
23856            self.write("(");
23857            for (i, col) in e.columns.iter().enumerate() {
23858                if i > 0 {
23859                    self.write(", ");
23860                }
23861                self.write(col);
23862            }
23863            self.write(")");
23864        }
23865        if let Some(partition) = &e.partition {
23866            self.write_space();
23867            self.generate_expression(partition)?;
23868        }
23869        if let Some(mode) = &e.mode {
23870            self.write_space();
23871            self.generate_expression(mode)?;
23872        }
23873        if let Some(expression) = &e.expression {
23874            self.write_space();
23875            self.generate_expression(expression)?;
23876        }
23877        if !e.properties.is_empty() {
23878            self.write_space();
23879            self.write_keyword(self.config.with_properties_prefix);
23880            self.write(" (");
23881            for (i, prop) in e.properties.iter().enumerate() {
23882                if i > 0 {
23883                    self.write(", ");
23884                }
23885                self.generate_expression(prop)?;
23886            }
23887            self.write(")");
23888        }
23889        Ok(())
23890    }
23891
23892    fn generate_analyze_delete(&mut self, e: &AnalyzeDelete) -> Result<()> {
23893        // Python: return f"DELETE{kind} STATISTICS"
23894        self.write_keyword("DELETE");
23895        if let Some(kind) = &e.kind {
23896            self.write_space();
23897            self.write_keyword(kind);
23898        }
23899        self.write_space();
23900        self.write_keyword("STATISTICS");
23901        Ok(())
23902    }
23903
23904    fn generate_analyze_histogram(&mut self, e: &AnalyzeHistogram) -> Result<()> {
23905        // Python: return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
23906        // Write `this` (UPDATE or DROP) as keyword to avoid quoting reserved words
23907        if let Expression::Identifier(id) = e.this.as_ref() {
23908            self.write_keyword(&id.name);
23909        } else {
23910            self.generate_expression(&e.this)?;
23911        }
23912        self.write_space();
23913        self.write_keyword("HISTOGRAM ON");
23914        self.write_space();
23915        for (i, expr) in e.expressions.iter().enumerate() {
23916            if i > 0 {
23917                self.write(", ");
23918            }
23919            self.generate_expression(expr)?;
23920        }
23921        if let Some(expression) = &e.expression {
23922            self.write_space();
23923            self.generate_expression(expression)?;
23924        }
23925        if let Some(update_options) = &e.update_options {
23926            self.write_space();
23927            self.generate_expression(update_options)?;
23928            self.write_space();
23929            self.write_keyword("UPDATE");
23930        }
23931        Ok(())
23932    }
23933
23934    fn generate_analyze_list_chained_rows(&mut self, e: &AnalyzeListChainedRows) -> Result<()> {
23935        // Python: return f"LIST CHAINED ROWS{inner_expression}"
23936        self.write_keyword("LIST CHAINED ROWS");
23937        if let Some(expression) = &e.expression {
23938            self.write_space();
23939            self.write_keyword("INTO");
23940            self.write_space();
23941            self.generate_expression(expression)?;
23942        }
23943        Ok(())
23944    }
23945
23946    fn generate_analyze_sample(&mut self, e: &AnalyzeSample) -> Result<()> {
23947        // Python: return f"SAMPLE {sample} {kind}"
23948        self.write_keyword("SAMPLE");
23949        self.write_space();
23950        if let Some(sample) = &e.sample {
23951            self.generate_expression(sample)?;
23952            self.write_space();
23953        }
23954        self.write_keyword(&e.kind);
23955        Ok(())
23956    }
23957
23958    fn generate_analyze_statistics(&mut self, e: &AnalyzeStatistics) -> Result<()> {
23959        // Python: return f"{kind}{option} STATISTICS{this}{columns}"
23960        self.write_keyword(&e.kind);
23961        if let Some(option) = &e.option {
23962            self.write_space();
23963            self.generate_expression(option)?;
23964        }
23965        self.write_space();
23966        self.write_keyword("STATISTICS");
23967        if let Some(this) = &e.this {
23968            self.write_space();
23969            self.generate_expression(this)?;
23970        }
23971        if !e.expressions.is_empty() {
23972            self.write_space();
23973            for (i, expr) in e.expressions.iter().enumerate() {
23974                if i > 0 {
23975                    self.write(", ");
23976                }
23977                self.generate_expression(expr)?;
23978            }
23979        }
23980        Ok(())
23981    }
23982
23983    fn generate_analyze_validate(&mut self, e: &AnalyzeValidate) -> Result<()> {
23984        // Python: return f"VALIDATE {kind}{this}{inner_expression}"
23985        self.write_keyword("VALIDATE");
23986        self.write_space();
23987        self.write_keyword(&e.kind);
23988        if let Some(this) = &e.this {
23989            self.write_space();
23990            // this is a keyword string like "UPDATE", "CASCADE FAST", etc. - write as keywords
23991            if let Expression::Identifier(id) = this.as_ref() {
23992                self.write_keyword(&id.name);
23993            } else {
23994                self.generate_expression(this)?;
23995            }
23996        }
23997        if let Some(expression) = &e.expression {
23998            self.write_space();
23999            self.write_keyword("INTO");
24000            self.write_space();
24001            self.generate_expression(expression)?;
24002        }
24003        Ok(())
24004    }
24005
24006    fn generate_analyze_with(&mut self, e: &AnalyzeWith) -> Result<()> {
24007        // Python: return f"WITH {expressions}"
24008        self.write_keyword("WITH");
24009        self.write_space();
24010        for (i, expr) in e.expressions.iter().enumerate() {
24011            if i > 0 {
24012                self.write(", ");
24013            }
24014            self.generate_expression(expr)?;
24015        }
24016        Ok(())
24017    }
24018
24019    fn generate_anonymous(&mut self, e: &Anonymous) -> Result<()> {
24020        // Anonymous represents a generic function call: FUNC_NAME(args...)
24021        // Python: return self.func(self.sql(expression, "this"), *expression.expressions)
24022        self.generate_expression(&e.this)?;
24023        self.write("(");
24024        for (i, arg) in e.expressions.iter().enumerate() {
24025            if i > 0 {
24026                self.write(", ");
24027            }
24028            self.generate_expression(arg)?;
24029        }
24030        self.write(")");
24031        Ok(())
24032    }
24033
24034    fn generate_anonymous_agg_func(&mut self, e: &AnonymousAggFunc) -> Result<()> {
24035        // Same as Anonymous but for aggregate functions
24036        self.generate_expression(&e.this)?;
24037        self.write("(");
24038        for (i, arg) in e.expressions.iter().enumerate() {
24039            if i > 0 {
24040                self.write(", ");
24041            }
24042            self.generate_expression(arg)?;
24043        }
24044        self.write(")");
24045        Ok(())
24046    }
24047
24048    fn generate_apply(&mut self, e: &Apply) -> Result<()> {
24049        // Python: return f"{this} APPLY({expr})"
24050        self.generate_expression(&e.this)?;
24051        self.write_space();
24052        self.write_keyword("APPLY");
24053        self.write("(");
24054        self.generate_expression(&e.expression)?;
24055        self.write(")");
24056        Ok(())
24057    }
24058
24059    fn generate_approx_percentile_estimate(&mut self, e: &ApproxPercentileEstimate) -> Result<()> {
24060        // APPROX_PERCENTILE_ESTIMATE(this, percentile)
24061        self.write_keyword("APPROX_PERCENTILE_ESTIMATE");
24062        self.write("(");
24063        self.generate_expression(&e.this)?;
24064        if let Some(percentile) = &e.percentile {
24065            self.write(", ");
24066            self.generate_expression(percentile)?;
24067        }
24068        self.write(")");
24069        Ok(())
24070    }
24071
24072    fn generate_approx_quantile(&mut self, e: &ApproxQuantile) -> Result<()> {
24073        // APPROX_QUANTILE(this, quantile[, accuracy][, weight])
24074        self.write_keyword("APPROX_QUANTILE");
24075        self.write("(");
24076        self.generate_expression(&e.this)?;
24077        if let Some(quantile) = &e.quantile {
24078            self.write(", ");
24079            self.generate_expression(quantile)?;
24080        }
24081        if let Some(accuracy) = &e.accuracy {
24082            self.write(", ");
24083            self.generate_expression(accuracy)?;
24084        }
24085        if let Some(weight) = &e.weight {
24086            self.write(", ");
24087            self.generate_expression(weight)?;
24088        }
24089        self.write(")");
24090        Ok(())
24091    }
24092
24093    fn generate_approx_quantiles(&mut self, e: &ApproxQuantiles) -> Result<()> {
24094        // APPROX_QUANTILES(this, expression)
24095        self.write_keyword("APPROX_QUANTILES");
24096        self.write("(");
24097        self.generate_expression(&e.this)?;
24098        if let Some(expression) = &e.expression {
24099            self.write(", ");
24100            self.generate_expression(expression)?;
24101        }
24102        self.write(")");
24103        Ok(())
24104    }
24105
24106    fn generate_approx_top_k(&mut self, e: &ApproxTopK) -> Result<()> {
24107        // APPROX_TOP_K(this[, expression][, counters])
24108        self.write_keyword("APPROX_TOP_K");
24109        self.write("(");
24110        self.generate_expression(&e.this)?;
24111        if let Some(expression) = &e.expression {
24112            self.write(", ");
24113            self.generate_expression(expression)?;
24114        }
24115        if let Some(counters) = &e.counters {
24116            self.write(", ");
24117            self.generate_expression(counters)?;
24118        }
24119        self.write(")");
24120        Ok(())
24121    }
24122
24123    fn generate_approx_top_k_accumulate(&mut self, e: &ApproxTopKAccumulate) -> Result<()> {
24124        // APPROX_TOP_K_ACCUMULATE(this[, expression])
24125        self.write_keyword("APPROX_TOP_K_ACCUMULATE");
24126        self.write("(");
24127        self.generate_expression(&e.this)?;
24128        if let Some(expression) = &e.expression {
24129            self.write(", ");
24130            self.generate_expression(expression)?;
24131        }
24132        self.write(")");
24133        Ok(())
24134    }
24135
24136    fn generate_approx_top_k_combine(&mut self, e: &ApproxTopKCombine) -> Result<()> {
24137        // APPROX_TOP_K_COMBINE(this[, expression])
24138        self.write_keyword("APPROX_TOP_K_COMBINE");
24139        self.write("(");
24140        self.generate_expression(&e.this)?;
24141        if let Some(expression) = &e.expression {
24142            self.write(", ");
24143            self.generate_expression(expression)?;
24144        }
24145        self.write(")");
24146        Ok(())
24147    }
24148
24149    fn generate_approx_top_k_estimate(&mut self, e: &ApproxTopKEstimate) -> Result<()> {
24150        // APPROX_TOP_K_ESTIMATE(this[, expression])
24151        self.write_keyword("APPROX_TOP_K_ESTIMATE");
24152        self.write("(");
24153        self.generate_expression(&e.this)?;
24154        if let Some(expression) = &e.expression {
24155            self.write(", ");
24156            self.generate_expression(expression)?;
24157        }
24158        self.write(")");
24159        Ok(())
24160    }
24161
24162    fn generate_approx_top_sum(&mut self, e: &ApproxTopSum) -> Result<()> {
24163        // APPROX_TOP_SUM(this, expression[, count])
24164        self.write_keyword("APPROX_TOP_SUM");
24165        self.write("(");
24166        self.generate_expression(&e.this)?;
24167        self.write(", ");
24168        self.generate_expression(&e.expression)?;
24169        if let Some(count) = &e.count {
24170            self.write(", ");
24171            self.generate_expression(count)?;
24172        }
24173        self.write(")");
24174        Ok(())
24175    }
24176
24177    fn generate_arg_max(&mut self, e: &ArgMax) -> Result<()> {
24178        // ARG_MAX(this, expression[, count])
24179        self.write_keyword("ARG_MAX");
24180        self.write("(");
24181        self.generate_expression(&e.this)?;
24182        self.write(", ");
24183        self.generate_expression(&e.expression)?;
24184        if let Some(count) = &e.count {
24185            self.write(", ");
24186            self.generate_expression(count)?;
24187        }
24188        self.write(")");
24189        Ok(())
24190    }
24191
24192    fn generate_arg_min(&mut self, e: &ArgMin) -> Result<()> {
24193        // ARG_MIN(this, expression[, count])
24194        self.write_keyword("ARG_MIN");
24195        self.write("(");
24196        self.generate_expression(&e.this)?;
24197        self.write(", ");
24198        self.generate_expression(&e.expression)?;
24199        if let Some(count) = &e.count {
24200            self.write(", ");
24201            self.generate_expression(count)?;
24202        }
24203        self.write(")");
24204        Ok(())
24205    }
24206
24207    fn generate_array_all(&mut self, e: &ArrayAll) -> Result<()> {
24208        // ARRAY_ALL(this, expression)
24209        self.write_keyword("ARRAY_ALL");
24210        self.write("(");
24211        self.generate_expression(&e.this)?;
24212        self.write(", ");
24213        self.generate_expression(&e.expression)?;
24214        self.write(")");
24215        Ok(())
24216    }
24217
24218    fn generate_array_any(&mut self, e: &ArrayAny) -> Result<()> {
24219        // ARRAY_ANY(this, expression) - fallback implementation
24220        self.write_keyword("ARRAY_ANY");
24221        self.write("(");
24222        self.generate_expression(&e.this)?;
24223        self.write(", ");
24224        self.generate_expression(&e.expression)?;
24225        self.write(")");
24226        Ok(())
24227    }
24228
24229    fn generate_array_construct_compact(&mut self, e: &ArrayConstructCompact) -> Result<()> {
24230        // ARRAY_CONSTRUCT_COMPACT(expressions...)
24231        self.write_keyword("ARRAY_CONSTRUCT_COMPACT");
24232        self.write("(");
24233        for (i, expr) in e.expressions.iter().enumerate() {
24234            if i > 0 {
24235                self.write(", ");
24236            }
24237            self.generate_expression(expr)?;
24238        }
24239        self.write(")");
24240        Ok(())
24241    }
24242
24243    fn generate_array_sum(&mut self, e: &ArraySum) -> Result<()> {
24244        // ARRAY_SUM(this[, expression])
24245        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
24246            self.write("arraySum");
24247        } else {
24248            self.write_keyword("ARRAY_SUM");
24249        }
24250        self.write("(");
24251        self.generate_expression(&e.this)?;
24252        if let Some(expression) = &e.expression {
24253            self.write(", ");
24254            self.generate_expression(expression)?;
24255        }
24256        self.write(")");
24257        Ok(())
24258    }
24259
24260    fn generate_at_index(&mut self, e: &AtIndex) -> Result<()> {
24261        // Python: return f"{this} AT {index}"
24262        self.generate_expression(&e.this)?;
24263        self.write_space();
24264        self.write_keyword("AT");
24265        self.write_space();
24266        self.generate_expression(&e.expression)?;
24267        Ok(())
24268    }
24269
24270    fn generate_attach(&mut self, e: &Attach) -> Result<()> {
24271        // Python: return f"ATTACH{exists_sql} {this}{expressions}"
24272        self.write_keyword("ATTACH");
24273        if e.exists {
24274            self.write_space();
24275            self.write_keyword("IF NOT EXISTS");
24276        }
24277        self.write_space();
24278        self.generate_expression(&e.this)?;
24279        if !e.expressions.is_empty() {
24280            self.write(" (");
24281            for (i, expr) in e.expressions.iter().enumerate() {
24282                if i > 0 {
24283                    self.write(", ");
24284                }
24285                self.generate_expression(expr)?;
24286            }
24287            self.write(")");
24288        }
24289        Ok(())
24290    }
24291
24292    fn generate_attach_option(&mut self, e: &AttachOption) -> Result<()> {
24293        // AttachOption: this [expression]
24294        // Python sqlglot: no equals sign, just space-separated
24295        self.generate_expression(&e.this)?;
24296        if let Some(expression) = &e.expression {
24297            self.write_space();
24298            self.generate_expression(expression)?;
24299        }
24300        Ok(())
24301    }
24302
24303    /// Generate the auto_increment keyword and options for a column definition.
24304    /// Different dialects use different syntax: IDENTITY, AUTOINCREMENT, AUTO_INCREMENT,
24305    /// GENERATED AS IDENTITY, etc.
24306    fn generate_auto_increment_keyword(
24307        &mut self,
24308        col: &crate::expressions::ColumnDef,
24309    ) -> Result<()> {
24310        use crate::dialects::DialectType;
24311        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
24312            self.write_keyword("IDENTITY");
24313            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
24314                self.write("(");
24315                if let Some(ref start) = col.auto_increment_start {
24316                    self.generate_expression(start)?;
24317                } else {
24318                    self.write("0");
24319                }
24320                self.write(", ");
24321                if let Some(ref inc) = col.auto_increment_increment {
24322                    self.generate_expression(inc)?;
24323                } else {
24324                    self.write("1");
24325                }
24326                self.write(")");
24327            }
24328        } else if matches!(
24329            self.config.dialect,
24330            Some(DialectType::Snowflake) | Some(DialectType::SQLite)
24331        ) {
24332            self.write_keyword("AUTOINCREMENT");
24333            if let Some(ref start) = col.auto_increment_start {
24334                self.write_space();
24335                self.write_keyword("START");
24336                self.write_space();
24337                self.generate_expression(start)?;
24338            }
24339            if let Some(ref inc) = col.auto_increment_increment {
24340                self.write_space();
24341                self.write_keyword("INCREMENT");
24342                self.write_space();
24343                self.generate_expression(inc)?;
24344            }
24345            if let Some(order) = col.auto_increment_order {
24346                self.write_space();
24347                if order {
24348                    self.write_keyword("ORDER");
24349                } else {
24350                    self.write_keyword("NOORDER");
24351                }
24352            }
24353        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
24354            self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
24355            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
24356                self.write(" (");
24357                let mut first = true;
24358                if let Some(ref start) = col.auto_increment_start {
24359                    self.write_keyword("START WITH");
24360                    self.write_space();
24361                    self.generate_expression(start)?;
24362                    first = false;
24363                }
24364                if let Some(ref inc) = col.auto_increment_increment {
24365                    if !first {
24366                        self.write_space();
24367                    }
24368                    self.write_keyword("INCREMENT BY");
24369                    self.write_space();
24370                    self.generate_expression(inc)?;
24371                }
24372                self.write(")");
24373            }
24374        } else if matches!(self.config.dialect, Some(DialectType::Databricks)) {
24375            self.write_keyword("GENERATED ALWAYS AS IDENTITY");
24376            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
24377                self.write(" (");
24378                let mut first = true;
24379                if let Some(ref start) = col.auto_increment_start {
24380                    self.write_keyword("START WITH");
24381                    self.write_space();
24382                    self.generate_expression(start)?;
24383                    first = false;
24384                }
24385                if let Some(ref inc) = col.auto_increment_increment {
24386                    if !first {
24387                        self.write_space();
24388                    }
24389                    self.write_keyword("INCREMENT BY");
24390                    self.write_space();
24391                    self.generate_expression(inc)?;
24392                }
24393                self.write(")");
24394            }
24395        } else if matches!(
24396            self.config.dialect,
24397            Some(DialectType::TSQL) | Some(DialectType::Fabric)
24398        ) {
24399            self.write_keyword("IDENTITY");
24400            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
24401                self.write("(");
24402                if let Some(ref start) = col.auto_increment_start {
24403                    self.generate_expression(start)?;
24404                } else {
24405                    self.write("0");
24406                }
24407                self.write(", ");
24408                if let Some(ref inc) = col.auto_increment_increment {
24409                    self.generate_expression(inc)?;
24410                } else {
24411                    self.write("1");
24412                }
24413                self.write(")");
24414            }
24415        } else {
24416            self.write_keyword("AUTO_INCREMENT");
24417            if let Some(ref start) = col.auto_increment_start {
24418                self.write_space();
24419                self.write_keyword("START");
24420                self.write_space();
24421                self.generate_expression(start)?;
24422            }
24423            if let Some(ref inc) = col.auto_increment_increment {
24424                self.write_space();
24425                self.write_keyword("INCREMENT");
24426                self.write_space();
24427                self.generate_expression(inc)?;
24428            }
24429            if let Some(order) = col.auto_increment_order {
24430                self.write_space();
24431                if order {
24432                    self.write_keyword("ORDER");
24433                } else {
24434                    self.write_keyword("NOORDER");
24435                }
24436            }
24437        }
24438        Ok(())
24439    }
24440
24441    fn generate_auto_increment_property(&mut self, e: &AutoIncrementProperty) -> Result<()> {
24442        // AUTO_INCREMENT=value
24443        self.write_keyword("AUTO_INCREMENT");
24444        self.write("=");
24445        self.generate_expression(&e.this)?;
24446        Ok(())
24447    }
24448
24449    fn generate_auto_refresh_property(&mut self, e: &AutoRefreshProperty) -> Result<()> {
24450        // AUTO_REFRESH=value
24451        self.write_keyword("AUTO_REFRESH");
24452        self.write("=");
24453        self.generate_expression(&e.this)?;
24454        Ok(())
24455    }
24456
24457    fn generate_backup_property(&mut self, e: &BackupProperty) -> Result<()> {
24458        // BACKUP YES|NO (Redshift syntax uses space, not equals)
24459        self.write_keyword("BACKUP");
24460        self.write_space();
24461        self.generate_expression(&e.this)?;
24462        Ok(())
24463    }
24464
24465    fn generate_base64_decode_binary(&mut self, e: &Base64DecodeBinary) -> Result<()> {
24466        // BASE64_DECODE_BINARY(this[, alphabet])
24467        self.write_keyword("BASE64_DECODE_BINARY");
24468        self.write("(");
24469        self.generate_expression(&e.this)?;
24470        if let Some(alphabet) = &e.alphabet {
24471            self.write(", ");
24472            self.generate_expression(alphabet)?;
24473        }
24474        self.write(")");
24475        Ok(())
24476    }
24477
24478    fn generate_base64_decode_string(&mut self, e: &Base64DecodeString) -> Result<()> {
24479        // BASE64_DECODE_STRING(this[, alphabet])
24480        self.write_keyword("BASE64_DECODE_STRING");
24481        self.write("(");
24482        self.generate_expression(&e.this)?;
24483        if let Some(alphabet) = &e.alphabet {
24484            self.write(", ");
24485            self.generate_expression(alphabet)?;
24486        }
24487        self.write(")");
24488        Ok(())
24489    }
24490
24491    fn generate_base64_encode(&mut self, e: &Base64Encode) -> Result<()> {
24492        // BASE64_ENCODE(this[, max_line_length][, alphabet])
24493        self.write_keyword("BASE64_ENCODE");
24494        self.write("(");
24495        self.generate_expression(&e.this)?;
24496        if let Some(max_line_length) = &e.max_line_length {
24497            self.write(", ");
24498            self.generate_expression(max_line_length)?;
24499        }
24500        if let Some(alphabet) = &e.alphabet {
24501            self.write(", ");
24502            self.generate_expression(alphabet)?;
24503        }
24504        self.write(")");
24505        Ok(())
24506    }
24507
24508    fn generate_block_compression_property(&mut self, e: &BlockCompressionProperty) -> Result<()> {
24509        // BLOCKCOMPRESSION=... (complex Teradata property)
24510        self.write_keyword("BLOCKCOMPRESSION");
24511        self.write("=");
24512        if let Some(autotemp) = &e.autotemp {
24513            self.write_keyword("AUTOTEMP");
24514            self.write("(");
24515            self.generate_expression(autotemp)?;
24516            self.write(")");
24517        }
24518        if let Some(always) = &e.always {
24519            self.generate_expression(always)?;
24520        }
24521        if let Some(default) = &e.default {
24522            self.generate_expression(default)?;
24523        }
24524        if let Some(manual) = &e.manual {
24525            self.generate_expression(manual)?;
24526        }
24527        if let Some(never) = &e.never {
24528            self.generate_expression(never)?;
24529        }
24530        Ok(())
24531    }
24532
24533    fn generate_booland(&mut self, e: &Booland) -> Result<()> {
24534        // Python: return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
24535        self.write("((");
24536        self.generate_expression(&e.this)?;
24537        self.write(") ");
24538        self.write_keyword("AND");
24539        self.write(" (");
24540        self.generate_expression(&e.expression)?;
24541        self.write("))");
24542        Ok(())
24543    }
24544
24545    fn generate_boolor(&mut self, e: &Boolor) -> Result<()> {
24546        // Python: return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
24547        self.write("((");
24548        self.generate_expression(&e.this)?;
24549        self.write(") ");
24550        self.write_keyword("OR");
24551        self.write(" (");
24552        self.generate_expression(&e.expression)?;
24553        self.write("))");
24554        Ok(())
24555    }
24556
24557    fn generate_build_property(&mut self, e: &BuildProperty) -> Result<()> {
24558        // BUILD value (e.g., BUILD IMMEDIATE, BUILD DEFERRED)
24559        self.write_keyword("BUILD");
24560        self.write_space();
24561        self.generate_expression(&e.this)?;
24562        Ok(())
24563    }
24564
24565    fn generate_byte_string(&mut self, e: &ByteString) -> Result<()> {
24566        // Byte string literal like B'...' or X'...'
24567        self.generate_expression(&e.this)?;
24568        Ok(())
24569    }
24570
24571    fn generate_case_specific_column_constraint(
24572        &mut self,
24573        e: &CaseSpecificColumnConstraint,
24574    ) -> Result<()> {
24575        // CASESPECIFIC or NOT CASESPECIFIC (Teradata)
24576        if e.not_.is_some() {
24577            self.write_keyword("NOT");
24578            self.write_space();
24579        }
24580        self.write_keyword("CASESPECIFIC");
24581        Ok(())
24582    }
24583
24584    fn generate_cast_to_str_type(&mut self, e: &CastToStrType) -> Result<()> {
24585        // Cast to string type (dialect-specific)
24586        self.write_keyword("CAST");
24587        self.write("(");
24588        self.generate_expression(&e.this)?;
24589        if self.config.dialect == Some(DialectType::ClickHouse) {
24590            // ClickHouse: CAST(expr, 'type_string')
24591            self.write(", ");
24592        } else {
24593            self.write_space();
24594            self.write_keyword("AS");
24595            self.write_space();
24596        }
24597        if let Some(to) = &e.to {
24598            self.generate_expression(to)?;
24599        }
24600        self.write(")");
24601        Ok(())
24602    }
24603
24604    fn generate_changes(&mut self, e: &Changes) -> Result<()> {
24605        // CHANGES (INFORMATION => value) AT|BEFORE (...) END (...)
24606        // Python: f"CHANGES ({information}){at_before}{end}"
24607        self.write_keyword("CHANGES");
24608        self.write(" (");
24609        if let Some(information) = &e.information {
24610            self.write_keyword("INFORMATION");
24611            self.write(" => ");
24612            self.generate_expression(information)?;
24613        }
24614        self.write(")");
24615        // at_before and end are HistoricalData expressions that generate their own keywords
24616        if let Some(at_before) = &e.at_before {
24617            self.write(" ");
24618            self.generate_expression(at_before)?;
24619        }
24620        if let Some(end) = &e.end {
24621            self.write(" ");
24622            self.generate_expression(end)?;
24623        }
24624        Ok(())
24625    }
24626
24627    fn generate_character_set_column_constraint(
24628        &mut self,
24629        e: &CharacterSetColumnConstraint,
24630    ) -> Result<()> {
24631        // CHARACTER SET charset_name
24632        self.write_keyword("CHARACTER SET");
24633        self.write_space();
24634        self.generate_expression(&e.this)?;
24635        Ok(())
24636    }
24637
24638    fn generate_character_set_property(&mut self, e: &CharacterSetProperty) -> Result<()> {
24639        // [DEFAULT] CHARACTER SET=value
24640        if e.default.is_some() {
24641            self.write_keyword("DEFAULT");
24642            self.write_space();
24643        }
24644        self.write_keyword("CHARACTER SET");
24645        self.write("=");
24646        self.generate_expression(&e.this)?;
24647        Ok(())
24648    }
24649
24650    fn generate_check_column_constraint(&mut self, e: &CheckColumnConstraint) -> Result<()> {
24651        // Python: return f"CHECK ({self.sql(expression, 'this')}){enforced}"
24652        self.write_keyword("CHECK");
24653        self.write(" (");
24654        self.generate_expression(&e.this)?;
24655        self.write(")");
24656        if e.enforced.is_some() {
24657            self.write_space();
24658            self.write_keyword("ENFORCED");
24659        }
24660        Ok(())
24661    }
24662
24663    fn generate_check_json(&mut self, e: &CheckJson) -> Result<()> {
24664        // CHECK_JSON(this)
24665        self.write_keyword("CHECK_JSON");
24666        self.write("(");
24667        self.generate_expression(&e.this)?;
24668        self.write(")");
24669        Ok(())
24670    }
24671
24672    fn generate_check_xml(&mut self, e: &CheckXml) -> Result<()> {
24673        // CHECK_XML(this)
24674        self.write_keyword("CHECK_XML");
24675        self.write("(");
24676        self.generate_expression(&e.this)?;
24677        self.write(")");
24678        Ok(())
24679    }
24680
24681    fn generate_checksum_property(&mut self, e: &ChecksumProperty) -> Result<()> {
24682        // CHECKSUM=[ON|OFF|DEFAULT]
24683        self.write_keyword("CHECKSUM");
24684        self.write("=");
24685        if e.on.is_some() {
24686            self.write_keyword("ON");
24687        } else if e.default.is_some() {
24688            self.write_keyword("DEFAULT");
24689        } else {
24690            self.write_keyword("OFF");
24691        }
24692        Ok(())
24693    }
24694
24695    fn generate_clone(&mut self, e: &Clone) -> Result<()> {
24696        // Python: return f"{shallow}{keyword} {this}"
24697        if e.shallow.is_some() {
24698            self.write_keyword("SHALLOW");
24699            self.write_space();
24700        }
24701        if e.copy.is_some() {
24702            self.write_keyword("COPY");
24703        } else {
24704            self.write_keyword("CLONE");
24705        }
24706        self.write_space();
24707        self.generate_expression(&e.this)?;
24708        Ok(())
24709    }
24710
24711    fn generate_cluster_by(&mut self, e: &ClusterBy) -> Result<()> {
24712        // CLUSTER BY (expressions)
24713        self.write_keyword("CLUSTER BY");
24714        self.write(" (");
24715        for (i, ord) in e.expressions.iter().enumerate() {
24716            if i > 0 {
24717                self.write(", ");
24718            }
24719            self.generate_ordered(ord)?;
24720        }
24721        self.write(")");
24722        Ok(())
24723    }
24724
24725    fn generate_clustered_by_property(&mut self, e: &ClusteredByProperty) -> Result<()> {
24726        // Python: return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
24727        self.write_keyword("CLUSTERED BY");
24728        self.write(" (");
24729        for (i, expr) in e.expressions.iter().enumerate() {
24730            if i > 0 {
24731                self.write(", ");
24732            }
24733            self.generate_expression(expr)?;
24734        }
24735        self.write(")");
24736        if let Some(sorted_by) = &e.sorted_by {
24737            self.write_space();
24738            self.write_keyword("SORTED BY");
24739            self.write(" (");
24740            // Unwrap Tuple to avoid double parentheses
24741            if let Expression::Tuple(t) = sorted_by.as_ref() {
24742                for (i, expr) in t.expressions.iter().enumerate() {
24743                    if i > 0 {
24744                        self.write(", ");
24745                    }
24746                    self.generate_expression(expr)?;
24747                }
24748            } else {
24749                self.generate_expression(sorted_by)?;
24750            }
24751            self.write(")");
24752        }
24753        if let Some(buckets) = &e.buckets {
24754            self.write_space();
24755            self.write_keyword("INTO");
24756            self.write_space();
24757            self.generate_expression(buckets)?;
24758            self.write_space();
24759            self.write_keyword("BUCKETS");
24760        }
24761        Ok(())
24762    }
24763
24764    fn generate_collate_property(&mut self, e: &CollateProperty) -> Result<()> {
24765        // [DEFAULT] COLLATE [=] value
24766        // BigQuery uses space: DEFAULT COLLATE 'en'
24767        // Others use equals: COLLATE='en'
24768        if e.default.is_some() {
24769            self.write_keyword("DEFAULT");
24770            self.write_space();
24771        }
24772        self.write_keyword("COLLATE");
24773        // BigQuery uses space between COLLATE and value
24774        match self.config.dialect {
24775            Some(DialectType::BigQuery) => self.write_space(),
24776            _ => self.write("="),
24777        }
24778        self.generate_expression(&e.this)?;
24779        Ok(())
24780    }
24781
24782    fn generate_column_constraint(&mut self, e: &ColumnConstraint) -> Result<()> {
24783        // ColumnConstraint is an enum
24784        match e {
24785            ColumnConstraint::NotNull => {
24786                self.write_keyword("NOT NULL");
24787            }
24788            ColumnConstraint::Null => {
24789                self.write_keyword("NULL");
24790            }
24791            ColumnConstraint::Unique => {
24792                self.write_keyword("UNIQUE");
24793            }
24794            ColumnConstraint::PrimaryKey => {
24795                self.write_keyword("PRIMARY KEY");
24796            }
24797            ColumnConstraint::Default(expr) => {
24798                self.write_keyword("DEFAULT");
24799                self.write_space();
24800                self.generate_expression(expr)?;
24801            }
24802            ColumnConstraint::Check(expr) => {
24803                self.write_keyword("CHECK");
24804                self.write(" (");
24805                self.generate_expression(expr)?;
24806                self.write(")");
24807            }
24808            ColumnConstraint::References(fk_ref) => {
24809                if fk_ref.has_foreign_key_keywords {
24810                    self.write_keyword("FOREIGN KEY");
24811                    self.write_space();
24812                }
24813                self.write_keyword("REFERENCES");
24814                self.write_space();
24815                self.generate_table(&fk_ref.table)?;
24816                if !fk_ref.columns.is_empty() {
24817                    self.write(" (");
24818                    for (i, col) in fk_ref.columns.iter().enumerate() {
24819                        if i > 0 {
24820                            self.write(", ");
24821                        }
24822                        self.generate_identifier(col)?;
24823                    }
24824                    self.write(")");
24825                }
24826            }
24827            ColumnConstraint::GeneratedAsIdentity(gen) => {
24828                self.write_keyword("GENERATED");
24829                self.write_space();
24830                if gen.always {
24831                    self.write_keyword("ALWAYS");
24832                } else {
24833                    self.write_keyword("BY DEFAULT");
24834                    if gen.on_null {
24835                        self.write_space();
24836                        self.write_keyword("ON NULL");
24837                    }
24838                }
24839                self.write_space();
24840                self.write_keyword("AS IDENTITY");
24841            }
24842            ColumnConstraint::Collate(collation) => {
24843                self.write_keyword("COLLATE");
24844                self.write_space();
24845                self.generate_identifier(collation)?;
24846            }
24847            ColumnConstraint::Comment(comment) => {
24848                self.write_keyword("COMMENT");
24849                self.write(" '");
24850                self.write(comment);
24851                self.write("'");
24852            }
24853            ColumnConstraint::ComputedColumn(cc) => {
24854                self.generate_computed_column_inline(cc)?;
24855            }
24856            ColumnConstraint::GeneratedAsRow(gar) => {
24857                self.generate_generated_as_row_inline(gar)?;
24858            }
24859            ColumnConstraint::Tags(tags) => {
24860                self.write_keyword("TAG");
24861                self.write(" (");
24862                for (i, expr) in tags.expressions.iter().enumerate() {
24863                    if i > 0 {
24864                        self.write(", ");
24865                    }
24866                    self.generate_expression(expr)?;
24867                }
24868                self.write(")");
24869            }
24870            ColumnConstraint::Path(path_expr) => {
24871                self.write_keyword("PATH");
24872                self.write_space();
24873                self.generate_expression(path_expr)?;
24874            }
24875        }
24876        Ok(())
24877    }
24878
24879    fn generate_column_position(&mut self, e: &ColumnPosition) -> Result<()> {
24880        // ColumnPosition is an enum
24881        match e {
24882            ColumnPosition::First => {
24883                self.write_keyword("FIRST");
24884            }
24885            ColumnPosition::After(ident) => {
24886                self.write_keyword("AFTER");
24887                self.write_space();
24888                self.generate_identifier(ident)?;
24889            }
24890        }
24891        Ok(())
24892    }
24893
24894    fn generate_column_prefix(&mut self, e: &ColumnPrefix) -> Result<()> {
24895        // column(prefix)
24896        self.generate_expression(&e.this)?;
24897        self.write("(");
24898        self.generate_expression(&e.expression)?;
24899        self.write(")");
24900        Ok(())
24901    }
24902
24903    fn generate_columns(&mut self, e: &Columns) -> Result<()> {
24904        // If unpack is true, this came from * COLUMNS(pattern)
24905        // DuckDB syntax: * COLUMNS(c ILIKE '%suffix') or COLUMNS(pattern)
24906        if let Some(ref unpack) = e.unpack {
24907            if let Expression::Boolean(b) = unpack.as_ref() {
24908                if b.value {
24909                    self.write("*");
24910                }
24911            }
24912        }
24913        self.write_keyword("COLUMNS");
24914        self.write("(");
24915        self.generate_expression(&e.this)?;
24916        self.write(")");
24917        Ok(())
24918    }
24919
24920    fn generate_combined_agg_func(&mut self, e: &CombinedAggFunc) -> Result<()> {
24921        // Combined aggregate: FUNC(args) combined
24922        self.generate_expression(&e.this)?;
24923        self.write("(");
24924        for (i, expr) in e.expressions.iter().enumerate() {
24925            if i > 0 {
24926                self.write(", ");
24927            }
24928            self.generate_expression(expr)?;
24929        }
24930        self.write(")");
24931        Ok(())
24932    }
24933
24934    fn generate_combined_parameterized_agg(&mut self, e: &CombinedParameterizedAgg) -> Result<()> {
24935        // Combined parameterized aggregate: FUNC(params)(expressions)
24936        self.generate_expression(&e.this)?;
24937        self.write("(");
24938        for (i, param) in e.params.iter().enumerate() {
24939            if i > 0 {
24940                self.write(", ");
24941            }
24942            self.generate_expression(param)?;
24943        }
24944        self.write(")(");
24945        for (i, expr) in e.expressions.iter().enumerate() {
24946            if i > 0 {
24947                self.write(", ");
24948            }
24949            self.generate_expression(expr)?;
24950        }
24951        self.write(")");
24952        Ok(())
24953    }
24954
24955    fn generate_commit(&mut self, e: &Commit) -> Result<()> {
24956        // COMMIT [TRANSACTION [transaction_name]] [WITH (DELAYED_DURABILITY = ON|OFF)] [AND [NO] CHAIN]
24957        self.write_keyword("COMMIT");
24958
24959        // TSQL always uses COMMIT TRANSACTION
24960        if e.this.is_none()
24961            && matches!(
24962                self.config.dialect,
24963                Some(DialectType::TSQL) | Some(DialectType::Fabric)
24964            )
24965        {
24966            self.write_space();
24967            self.write_keyword("TRANSACTION");
24968        }
24969
24970        // Check if this has TRANSACTION keyword or transaction name
24971        if let Some(this) = &e.this {
24972            // Check if it's just the "TRANSACTION" marker or an actual transaction name
24973            let is_transaction_marker = matches!(
24974                this.as_ref(),
24975                Expression::Identifier(id) if id.name == "TRANSACTION"
24976            );
24977
24978            self.write_space();
24979            self.write_keyword("TRANSACTION");
24980
24981            // If it's a real transaction name, output it
24982            if !is_transaction_marker {
24983                self.write_space();
24984                self.generate_expression(this)?;
24985            }
24986        }
24987
24988        // Output WITH (DELAYED_DURABILITY = ON|OFF) for TSQL
24989        if let Some(durability) = &e.durability {
24990            self.write_space();
24991            self.write_keyword("WITH");
24992            self.write(" (");
24993            self.write_keyword("DELAYED_DURABILITY");
24994            self.write(" = ");
24995            if let Expression::Boolean(BooleanLiteral { value: true }) = durability.as_ref() {
24996                self.write_keyword("ON");
24997            } else {
24998                self.write_keyword("OFF");
24999            }
25000            self.write(")");
25001        }
25002
25003        // Output AND [NO] CHAIN
25004        if let Some(chain) = &e.chain {
25005            self.write_space();
25006            if let Expression::Boolean(BooleanLiteral { value: false }) = chain.as_ref() {
25007                self.write_keyword("AND NO CHAIN");
25008            } else {
25009                self.write_keyword("AND CHAIN");
25010            }
25011        }
25012        Ok(())
25013    }
25014
25015    fn generate_comprehension(&mut self, e: &Comprehension) -> Result<()> {
25016        // Python-style comprehension: [expr FOR var[, pos] IN iterator IF condition]
25017        self.write("[");
25018        self.generate_expression(&e.this)?;
25019        self.write_space();
25020        self.write_keyword("FOR");
25021        self.write_space();
25022        self.generate_expression(&e.expression)?;
25023        // Handle optional position variable (for enumerate-like syntax)
25024        if let Some(pos) = &e.position {
25025            self.write(", ");
25026            self.generate_expression(pos)?;
25027        }
25028        if let Some(iterator) = &e.iterator {
25029            self.write_space();
25030            self.write_keyword("IN");
25031            self.write_space();
25032            self.generate_expression(iterator)?;
25033        }
25034        if let Some(condition) = &e.condition {
25035            self.write_space();
25036            self.write_keyword("IF");
25037            self.write_space();
25038            self.generate_expression(condition)?;
25039        }
25040        self.write("]");
25041        Ok(())
25042    }
25043
25044    fn generate_compress(&mut self, e: &Compress) -> Result<()> {
25045        // COMPRESS(this[, method])
25046        self.write_keyword("COMPRESS");
25047        self.write("(");
25048        self.generate_expression(&e.this)?;
25049        if let Some(method) = &e.method {
25050            self.write(", '");
25051            self.write(method);
25052            self.write("'");
25053        }
25054        self.write(")");
25055        Ok(())
25056    }
25057
25058    fn generate_compress_column_constraint(&mut self, e: &CompressColumnConstraint) -> Result<()> {
25059        // Python: return f"COMPRESS {this}"
25060        self.write_keyword("COMPRESS");
25061        if let Some(this) = &e.this {
25062            self.write_space();
25063            self.generate_expression(this)?;
25064        }
25065        Ok(())
25066    }
25067
25068    fn generate_computed_column_constraint(&mut self, e: &ComputedColumnConstraint) -> Result<()> {
25069        // Python: return f"AS {this}{persisted}"
25070        self.write_keyword("AS");
25071        self.write_space();
25072        self.generate_expression(&e.this)?;
25073        if e.not_null.is_some() {
25074            self.write_space();
25075            self.write_keyword("PERSISTED NOT NULL");
25076        } else if e.persisted.is_some() {
25077            self.write_space();
25078            self.write_keyword("PERSISTED");
25079        }
25080        Ok(())
25081    }
25082
25083    /// Generate a ComputedColumn constraint inline within a column definition.
25084    /// Handles MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
25085    /// Handles TSQL: AS (expr) [PERSISTED] [NOT NULL]
25086    fn generate_computed_column_inline(&mut self, cc: &ComputedColumn) -> Result<()> {
25087        let computed_expr = if matches!(
25088            self.config.dialect,
25089            Some(DialectType::TSQL) | Some(DialectType::Fabric)
25090        ) {
25091            match &*cc.expression {
25092                Expression::Year(y) if !matches!(&y.this, Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
25093                {
25094                    let wrapped = Expression::Cast(Box::new(Cast {
25095                        this: y.this.clone(),
25096                        to: DataType::Date,
25097                        trailing_comments: Vec::new(),
25098                        double_colon_syntax: false,
25099                        format: None,
25100                        default: None,
25101                    }));
25102                    Expression::Year(Box::new(UnaryFunc::new(wrapped)))
25103                }
25104                Expression::Function(f)
25105                    if f.name.eq_ignore_ascii_case("YEAR")
25106                        && f.args.len() == 1
25107                        && !matches!(&f.args[0], Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
25108                {
25109                    let wrapped = Expression::Cast(Box::new(Cast {
25110                        this: f.args[0].clone(),
25111                        to: DataType::Date,
25112                        trailing_comments: Vec::new(),
25113                        double_colon_syntax: false,
25114                        format: None,
25115                        default: None,
25116                    }));
25117                    Expression::Function(Box::new(Function::new("YEAR".to_string(), vec![wrapped])))
25118                }
25119                _ => *cc.expression.clone(),
25120            }
25121        } else {
25122            *cc.expression.clone()
25123        };
25124
25125        match cc.persistence_kind.as_deref() {
25126            Some("STORED") | Some("VIRTUAL") => {
25127                // MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
25128                self.write_keyword("GENERATED ALWAYS AS");
25129                self.write(" (");
25130                self.generate_expression(&computed_expr)?;
25131                self.write(")");
25132                self.write_space();
25133                if cc.persisted {
25134                    self.write_keyword("STORED");
25135                } else {
25136                    self.write_keyword("VIRTUAL");
25137                }
25138            }
25139            Some("PERSISTED") => {
25140                // TSQL/SingleStore: AS (expr) PERSISTED [TYPE] [NOT NULL]
25141                self.write_keyword("AS");
25142                self.write(" (");
25143                self.generate_expression(&computed_expr)?;
25144                self.write(")");
25145                self.write_space();
25146                self.write_keyword("PERSISTED");
25147                // Output data type if present (SingleStore: PERSISTED TYPE NOT NULL)
25148                if let Some(ref dt) = cc.data_type {
25149                    self.write_space();
25150                    self.generate_data_type(dt)?;
25151                }
25152                if cc.not_null {
25153                    self.write_space();
25154                    self.write_keyword("NOT NULL");
25155                }
25156            }
25157            _ => {
25158                // Spark/Databricks/Hive: GENERATED ALWAYS AS (expr)
25159                // TSQL computed column without PERSISTED: AS (expr)
25160                if matches!(
25161                    self.config.dialect,
25162                    Some(DialectType::Spark)
25163                        | Some(DialectType::Databricks)
25164                        | Some(DialectType::Hive)
25165                ) {
25166                    self.write_keyword("GENERATED ALWAYS AS");
25167                    self.write(" (");
25168                    self.generate_expression(&computed_expr)?;
25169                    self.write(")");
25170                } else if matches!(
25171                    self.config.dialect,
25172                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
25173                ) {
25174                    self.write_keyword("AS");
25175                    let omit_parens = matches!(computed_expr, Expression::Year(_))
25176                        || matches!(&computed_expr, Expression::Function(f) if f.name.eq_ignore_ascii_case("YEAR"));
25177                    if omit_parens {
25178                        self.write_space();
25179                        self.generate_expression(&computed_expr)?;
25180                    } else {
25181                        self.write(" (");
25182                        self.generate_expression(&computed_expr)?;
25183                        self.write(")");
25184                    }
25185                } else {
25186                    self.write_keyword("AS");
25187                    self.write(" (");
25188                    self.generate_expression(&computed_expr)?;
25189                    self.write(")");
25190                }
25191            }
25192        }
25193        Ok(())
25194    }
25195
25196    /// Generate a GeneratedAsRow constraint inline within a column definition.
25197    /// TSQL temporal: GENERATED ALWAYS AS ROW START|END [HIDDEN]
25198    fn generate_generated_as_row_inline(&mut self, gar: &GeneratedAsRow) -> Result<()> {
25199        self.write_keyword("GENERATED ALWAYS AS ROW ");
25200        if gar.start {
25201            self.write_keyword("START");
25202        } else {
25203            self.write_keyword("END");
25204        }
25205        if gar.hidden {
25206            self.write_space();
25207            self.write_keyword("HIDDEN");
25208        }
25209        Ok(())
25210    }
25211
25212    /// Generate just the SYSTEM_VERSIONING=ON(...) content without WITH() wrapper.
25213    fn generate_system_versioning_content(
25214        &mut self,
25215        e: &WithSystemVersioningProperty,
25216    ) -> Result<()> {
25217        let mut parts = Vec::new();
25218
25219        if let Some(this) = &e.this {
25220            let mut s = String::from("HISTORY_TABLE=");
25221            let mut gen = Generator::new();
25222            gen.config = self.config.clone();
25223            gen.generate_expression(this)?;
25224            s.push_str(&gen.output);
25225            parts.push(s);
25226        }
25227
25228        if let Some(data_consistency) = &e.data_consistency {
25229            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
25230            let mut gen = Generator::new();
25231            gen.config = self.config.clone();
25232            gen.generate_expression(data_consistency)?;
25233            s.push_str(&gen.output);
25234            parts.push(s);
25235        }
25236
25237        if let Some(retention_period) = &e.retention_period {
25238            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
25239            let mut gen = Generator::new();
25240            gen.config = self.config.clone();
25241            gen.generate_expression(retention_period)?;
25242            s.push_str(&gen.output);
25243            parts.push(s);
25244        }
25245
25246        self.write_keyword("SYSTEM_VERSIONING");
25247        self.write("=");
25248
25249        if !parts.is_empty() {
25250            self.write_keyword("ON");
25251            self.write("(");
25252            self.write(&parts.join(", "));
25253            self.write(")");
25254        } else if e.on.is_some() {
25255            self.write_keyword("ON");
25256        } else {
25257            self.write_keyword("OFF");
25258        }
25259
25260        Ok(())
25261    }
25262
25263    fn generate_conditional_insert(&mut self, e: &ConditionalInsert) -> Result<()> {
25264        // Conditional INSERT for multi-table inserts
25265        // Output: [WHEN cond THEN | ELSE] INTO table [(cols)] [VALUES (...)]
25266        if e.else_.is_some() {
25267            self.write_keyword("ELSE");
25268            self.write_space();
25269        } else if let Some(expression) = &e.expression {
25270            self.write_keyword("WHEN");
25271            self.write_space();
25272            self.generate_expression(expression)?;
25273            self.write_space();
25274            self.write_keyword("THEN");
25275            self.write_space();
25276        }
25277
25278        // Handle Insert expression specially - output "INTO table (cols) VALUES (...)"
25279        // without the "INSERT " prefix
25280        if let Expression::Insert(insert) = e.this.as_ref() {
25281            self.write_keyword("INTO");
25282            self.write_space();
25283            self.generate_table(&insert.table)?;
25284
25285            // Optional column list
25286            if !insert.columns.is_empty() {
25287                self.write(" (");
25288                for (i, col) in insert.columns.iter().enumerate() {
25289                    if i > 0 {
25290                        self.write(", ");
25291                    }
25292                    self.generate_identifier(col)?;
25293                }
25294                self.write(")");
25295            }
25296
25297            // Optional VALUES clause
25298            if !insert.values.is_empty() {
25299                self.write_space();
25300                self.write_keyword("VALUES");
25301                for (row_idx, row) in insert.values.iter().enumerate() {
25302                    if row_idx > 0 {
25303                        self.write(", ");
25304                    }
25305                    self.write(" (");
25306                    for (i, val) in row.iter().enumerate() {
25307                        if i > 0 {
25308                            self.write(", ");
25309                        }
25310                        self.generate_expression(val)?;
25311                    }
25312                    self.write(")");
25313                }
25314            }
25315        } else {
25316            // Fallback for non-Insert expressions
25317            self.generate_expression(&e.this)?;
25318        }
25319        Ok(())
25320    }
25321
25322    fn generate_constraint(&mut self, e: &Constraint) -> Result<()> {
25323        // Python: return f"CONSTRAINT {this} {expressions}"
25324        self.write_keyword("CONSTRAINT");
25325        self.write_space();
25326        self.generate_expression(&e.this)?;
25327        if !e.expressions.is_empty() {
25328            self.write_space();
25329            for (i, expr) in e.expressions.iter().enumerate() {
25330                if i > 0 {
25331                    self.write_space();
25332                }
25333                self.generate_expression(expr)?;
25334            }
25335        }
25336        Ok(())
25337    }
25338
25339    fn generate_convert_timezone(&mut self, e: &ConvertTimezone) -> Result<()> {
25340        // CONVERT_TIMEZONE([source_tz,] target_tz, timestamp)
25341        self.write_keyword("CONVERT_TIMEZONE");
25342        self.write("(");
25343        let mut first = true;
25344        if let Some(source_tz) = &e.source_tz {
25345            self.generate_expression(source_tz)?;
25346            first = false;
25347        }
25348        if let Some(target_tz) = &e.target_tz {
25349            if !first {
25350                self.write(", ");
25351            }
25352            self.generate_expression(target_tz)?;
25353            first = false;
25354        }
25355        if let Some(timestamp) = &e.timestamp {
25356            if !first {
25357                self.write(", ");
25358            }
25359            self.generate_expression(timestamp)?;
25360        }
25361        self.write(")");
25362        Ok(())
25363    }
25364
25365    fn generate_convert_to_charset(&mut self, e: &ConvertToCharset) -> Result<()> {
25366        // CONVERT(this USING dest)
25367        self.write_keyword("CONVERT");
25368        self.write("(");
25369        self.generate_expression(&e.this)?;
25370        if let Some(dest) = &e.dest {
25371            self.write_space();
25372            self.write_keyword("USING");
25373            self.write_space();
25374            self.generate_expression(dest)?;
25375        }
25376        self.write(")");
25377        Ok(())
25378    }
25379
25380    fn generate_copy(&mut self, e: &CopyStmt) -> Result<()> {
25381        self.write_keyword("COPY");
25382        if e.is_into {
25383            self.write_space();
25384            self.write_keyword("INTO");
25385        }
25386        self.write_space();
25387
25388        // Generate target table or query (or stage for COPY INTO @stage)
25389        if let Expression::Literal(Literal::String(s)) = &e.this {
25390            if s.starts_with('@') {
25391                self.write(s);
25392            } else {
25393                self.generate_expression(&e.this)?;
25394            }
25395        } else {
25396            self.generate_expression(&e.this)?;
25397        }
25398
25399        // FROM or TO based on kind
25400        if e.kind {
25401            // kind=true means FROM (loading into table)
25402            if self.config.pretty {
25403                self.write_newline();
25404            } else {
25405                self.write_space();
25406            }
25407            self.write_keyword("FROM");
25408            self.write_space();
25409        } else if !e.files.is_empty() {
25410            // kind=false means TO (exporting)
25411            if self.config.pretty {
25412                self.write_newline();
25413            } else {
25414                self.write_space();
25415            }
25416            self.write_keyword("TO");
25417            self.write_space();
25418        }
25419
25420        // Generate source/destination files
25421        for (i, file) in e.files.iter().enumerate() {
25422            if i > 0 {
25423                self.write_space();
25424            }
25425            // For stage references (strings starting with @), output without quotes
25426            if let Expression::Literal(Literal::String(s)) = file {
25427                if s.starts_with('@') {
25428                    self.write(s);
25429                } else {
25430                    self.generate_expression(file)?;
25431                }
25432            } else if let Expression::Identifier(id) = file {
25433                // Backtick-quoted file path (Databricks style: `s3://link`)
25434                if id.quoted {
25435                    self.write("`");
25436                    self.write(&id.name);
25437                    self.write("`");
25438                } else {
25439                    self.generate_expression(file)?;
25440                }
25441            } else {
25442                self.generate_expression(file)?;
25443            }
25444        }
25445
25446        // Generate credentials if present (Snowflake style - not wrapped in WITH)
25447        if !e.with_wrapped {
25448            if let Some(ref creds) = e.credentials {
25449                if let Some(ref storage) = creds.storage {
25450                    if self.config.pretty {
25451                        self.write_newline();
25452                    } else {
25453                        self.write_space();
25454                    }
25455                    self.write_keyword("STORAGE_INTEGRATION");
25456                    self.write(" = ");
25457                    self.write(storage);
25458                }
25459                if creds.credentials.is_empty() {
25460                    // Empty credentials: CREDENTIALS = ()
25461                    if self.config.pretty {
25462                        self.write_newline();
25463                    } else {
25464                        self.write_space();
25465                    }
25466                    self.write_keyword("CREDENTIALS");
25467                    self.write(" = ()");
25468                } else {
25469                    if self.config.pretty {
25470                        self.write_newline();
25471                    } else {
25472                        self.write_space();
25473                    }
25474                    self.write_keyword("CREDENTIALS");
25475                    // Check if this is Redshift-style (single value with empty key)
25476                    // vs Snowflake-style (multiple key=value pairs)
25477                    if creds.credentials.len() == 1 && creds.credentials[0].0.is_empty() {
25478                        // Redshift style: CREDENTIALS 'value'
25479                        self.write(" '");
25480                        self.write(&creds.credentials[0].1);
25481                        self.write("'");
25482                    } else {
25483                        // Snowflake style: CREDENTIALS = (KEY='value' ...)
25484                        self.write(" = (");
25485                        for (i, (k, v)) in creds.credentials.iter().enumerate() {
25486                            if i > 0 {
25487                                self.write_space();
25488                            }
25489                            self.write(k);
25490                            self.write("='");
25491                            self.write(v);
25492                            self.write("'");
25493                        }
25494                        self.write(")");
25495                    }
25496                }
25497                if let Some(ref encryption) = creds.encryption {
25498                    self.write_space();
25499                    self.write_keyword("ENCRYPTION");
25500                    self.write(" = ");
25501                    self.write(encryption);
25502                }
25503            }
25504        }
25505
25506        // Generate parameters
25507        if !e.params.is_empty() {
25508            if e.with_wrapped {
25509                // DuckDB/PostgreSQL/TSQL WITH (...) format
25510                self.write_space();
25511                self.write_keyword("WITH");
25512                self.write(" (");
25513                for (i, param) in e.params.iter().enumerate() {
25514                    if i > 0 {
25515                        self.write(", ");
25516                    }
25517                    self.generate_copy_param_with_format(param)?;
25518                }
25519                self.write(")");
25520            } else {
25521                // Snowflake/Redshift format: KEY = VALUE or KEY VALUE (space separated, no WITH wrapper)
25522                // For Redshift: IAM_ROLE value, CREDENTIALS 'value', REGION 'value', FORMAT type
25523                // For Snowflake: KEY = VALUE
25524                for param in &e.params {
25525                    if self.config.pretty {
25526                        self.write_newline();
25527                    } else {
25528                        self.write_space();
25529                    }
25530                    // Preserve original case of parameter name (important for Redshift COPY options)
25531                    self.write(&param.name);
25532                    if let Some(ref value) = param.value {
25533                        // Use = only if it was present in the original (param.eq)
25534                        if param.eq {
25535                            self.write(" = ");
25536                        } else {
25537                            self.write(" ");
25538                        }
25539                        if !param.values.is_empty() {
25540                            self.write("(");
25541                            for (i, v) in param.values.iter().enumerate() {
25542                                if i > 0 {
25543                                    self.write_space();
25544                                }
25545                                self.generate_copy_nested_param(v)?;
25546                            }
25547                            self.write(")");
25548                        } else {
25549                            // For COPY parameter values, output identifiers without quoting
25550                            self.generate_copy_param_value(value)?;
25551                        }
25552                    } else if !param.values.is_empty() {
25553                        // For varlen options like FORMAT_OPTIONS, COPY_OPTIONS - no = before (
25554                        if param.eq {
25555                            self.write(" = (");
25556                        } else {
25557                            self.write(" (");
25558                        }
25559                        // Determine separator for values inside parentheses:
25560                        // - Snowflake FILE_FORMAT = (TYPE=CSV FIELD_DELIMITER='|') → space-separated (has = before parens)
25561                        // - Databricks FORMAT_OPTIONS ('opt1'='true', 'opt2'='test') → comma-separated (no = before parens)
25562                        // - Simple value lists like FILES = ('file1', 'file2') → comma-separated
25563                        let is_key_value_pairs = param
25564                            .values
25565                            .first()
25566                            .map_or(false, |v| matches!(v, Expression::Eq(_)));
25567                        let sep = if is_key_value_pairs && param.eq {
25568                            " "
25569                        } else {
25570                            ", "
25571                        };
25572                        for (i, v) in param.values.iter().enumerate() {
25573                            if i > 0 {
25574                                self.write(sep);
25575                            }
25576                            self.generate_copy_nested_param(v)?;
25577                        }
25578                        self.write(")");
25579                    }
25580                }
25581            }
25582        }
25583
25584        Ok(())
25585    }
25586
25587    /// Generate a COPY parameter in WITH (...) format
25588    /// Handles both KEY = VALUE (TSQL) and KEY VALUE (DuckDB/PostgreSQL) formats
25589    fn generate_copy_param_with_format(&mut self, param: &CopyParameter) -> Result<()> {
25590        self.write_keyword(&param.name);
25591        if !param.values.is_empty() {
25592            // Nested values: CREDENTIAL = (IDENTITY='...', SECRET='...')
25593            self.write(" = (");
25594            for (i, v) in param.values.iter().enumerate() {
25595                if i > 0 {
25596                    self.write(", ");
25597                }
25598                self.generate_copy_nested_param(v)?;
25599            }
25600            self.write(")");
25601        } else if let Some(ref value) = param.value {
25602            if param.eq {
25603                self.write(" = ");
25604            } else {
25605                self.write(" ");
25606            }
25607            self.generate_expression(value)?;
25608        }
25609        Ok(())
25610    }
25611
25612    /// Generate nested parameter for COPY statements (KEY=VALUE without spaces)
25613    fn generate_copy_nested_param(&mut self, expr: &Expression) -> Result<()> {
25614        match expr {
25615            Expression::Eq(eq) => {
25616                // Generate key
25617                match &eq.left {
25618                    Expression::Column(c) => self.write(&c.name.name),
25619                    _ => self.generate_expression(&eq.left)?,
25620                }
25621                self.write("=");
25622                // Generate value
25623                match &eq.right {
25624                    Expression::Literal(Literal::String(s)) => {
25625                        self.write("'");
25626                        self.write(s);
25627                        self.write("'");
25628                    }
25629                    Expression::Tuple(t) => {
25630                        // For lists like NULL_IF=('', 'str1')
25631                        self.write("(");
25632                        if self.config.pretty {
25633                            self.write_newline();
25634                            self.indent_level += 1;
25635                            for (i, item) in t.expressions.iter().enumerate() {
25636                                if i > 0 {
25637                                    self.write(", ");
25638                                }
25639                                self.write_indent();
25640                                self.generate_expression(item)?;
25641                            }
25642                            self.write_newline();
25643                            self.indent_level -= 1;
25644                        } else {
25645                            for (i, item) in t.expressions.iter().enumerate() {
25646                                if i > 0 {
25647                                    self.write(", ");
25648                                }
25649                                self.generate_expression(item)?;
25650                            }
25651                        }
25652                        self.write(")");
25653                    }
25654                    _ => self.generate_expression(&eq.right)?,
25655                }
25656                Ok(())
25657            }
25658            Expression::Column(c) => {
25659                // Standalone keyword like COMPRESSION
25660                self.write(&c.name.name);
25661                Ok(())
25662            }
25663            _ => self.generate_expression(expr),
25664        }
25665    }
25666
25667    /// Generate a COPY parameter value, outputting identifiers/columns without quoting
25668    /// This is needed for Redshift-style COPY params like: IAM_ROLE default, FORMAT orc
25669    fn generate_copy_param_value(&mut self, expr: &Expression) -> Result<()> {
25670        match expr {
25671            Expression::Column(c) => {
25672                // Output identifier, preserving quotes if originally quoted
25673                if c.name.quoted {
25674                    self.write("\"");
25675                    self.write(&c.name.name);
25676                    self.write("\"");
25677                } else {
25678                    self.write(&c.name.name);
25679                }
25680                Ok(())
25681            }
25682            Expression::Identifier(id) => {
25683                // Output identifier, preserving quotes if originally quoted
25684                if id.quoted {
25685                    self.write("\"");
25686                    self.write(&id.name);
25687                    self.write("\"");
25688                } else {
25689                    self.write(&id.name);
25690                }
25691                Ok(())
25692            }
25693            Expression::Literal(Literal::String(s)) => {
25694                // Output string with quotes
25695                self.write("'");
25696                self.write(s);
25697                self.write("'");
25698                Ok(())
25699            }
25700            _ => self.generate_expression(expr),
25701        }
25702    }
25703
25704    fn generate_copy_parameter(&mut self, e: &CopyParameter) -> Result<()> {
25705        self.write_keyword(&e.name);
25706        if let Some(ref value) = e.value {
25707            if e.eq {
25708                self.write(" = ");
25709            } else {
25710                self.write(" ");
25711            }
25712            self.generate_expression(value)?;
25713        }
25714        if !e.values.is_empty() {
25715            if e.eq {
25716                self.write(" = ");
25717            } else {
25718                self.write(" ");
25719            }
25720            self.write("(");
25721            for (i, v) in e.values.iter().enumerate() {
25722                if i > 0 {
25723                    self.write(", ");
25724                }
25725                self.generate_expression(v)?;
25726            }
25727            self.write(")");
25728        }
25729        Ok(())
25730    }
25731
25732    fn generate_corr(&mut self, e: &Corr) -> Result<()> {
25733        // CORR(this, expression)
25734        self.write_keyword("CORR");
25735        self.write("(");
25736        self.generate_expression(&e.this)?;
25737        self.write(", ");
25738        self.generate_expression(&e.expression)?;
25739        self.write(")");
25740        Ok(())
25741    }
25742
25743    fn generate_cosine_distance(&mut self, e: &CosineDistance) -> Result<()> {
25744        // COSINE_DISTANCE(this, expression)
25745        self.write_keyword("COSINE_DISTANCE");
25746        self.write("(");
25747        self.generate_expression(&e.this)?;
25748        self.write(", ");
25749        self.generate_expression(&e.expression)?;
25750        self.write(")");
25751        Ok(())
25752    }
25753
25754    fn generate_covar_pop(&mut self, e: &CovarPop) -> Result<()> {
25755        // COVAR_POP(this, expression)
25756        self.write_keyword("COVAR_POP");
25757        self.write("(");
25758        self.generate_expression(&e.this)?;
25759        self.write(", ");
25760        self.generate_expression(&e.expression)?;
25761        self.write(")");
25762        Ok(())
25763    }
25764
25765    fn generate_covar_samp(&mut self, e: &CovarSamp) -> Result<()> {
25766        // COVAR_SAMP(this, expression)
25767        self.write_keyword("COVAR_SAMP");
25768        self.write("(");
25769        self.generate_expression(&e.this)?;
25770        self.write(", ");
25771        self.generate_expression(&e.expression)?;
25772        self.write(")");
25773        Ok(())
25774    }
25775
25776    fn generate_credentials(&mut self, e: &Credentials) -> Result<()> {
25777        // CREDENTIALS (key1='value1', key2='value2')
25778        self.write_keyword("CREDENTIALS");
25779        self.write(" (");
25780        for (i, (key, value)) in e.credentials.iter().enumerate() {
25781            if i > 0 {
25782                self.write(", ");
25783            }
25784            self.write(key);
25785            self.write("='");
25786            self.write(value);
25787            self.write("'");
25788        }
25789        self.write(")");
25790        Ok(())
25791    }
25792
25793    fn generate_credentials_property(&mut self, e: &CredentialsProperty) -> Result<()> {
25794        // CREDENTIALS=(expressions)
25795        self.write_keyword("CREDENTIALS");
25796        self.write("=(");
25797        for (i, expr) in e.expressions.iter().enumerate() {
25798            if i > 0 {
25799                self.write(", ");
25800            }
25801            self.generate_expression(expr)?;
25802        }
25803        self.write(")");
25804        Ok(())
25805    }
25806
25807    fn generate_cte(&mut self, e: &Cte) -> Result<()> {
25808        use crate::dialects::DialectType;
25809
25810        // Python: return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
25811        // Output: alias [(col1, col2, ...)] AS [MATERIALIZED|NOT MATERIALIZED] (subquery)
25812        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !e.alias_first {
25813            self.generate_expression(&e.this)?;
25814            self.write_space();
25815            self.write_keyword("AS");
25816            self.write_space();
25817            self.generate_identifier(&e.alias)?;
25818            return Ok(());
25819        }
25820        self.write(&e.alias.name);
25821
25822        // BigQuery doesn't support column aliases in CTE definitions
25823        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
25824
25825        if !e.columns.is_empty() && !skip_cte_columns {
25826            self.write("(");
25827            for (i, col) in e.columns.iter().enumerate() {
25828                if i > 0 {
25829                    self.write(", ");
25830                }
25831                self.write(&col.name);
25832            }
25833            self.write(")");
25834        }
25835        // USING KEY (columns) for DuckDB recursive CTEs
25836        if !e.key_expressions.is_empty() {
25837            self.write_space();
25838            self.write_keyword("USING KEY");
25839            self.write(" (");
25840            for (i, key) in e.key_expressions.iter().enumerate() {
25841                if i > 0 {
25842                    self.write(", ");
25843                }
25844                self.write(&key.name);
25845            }
25846            self.write(")");
25847        }
25848        self.write_space();
25849        self.write_keyword("AS");
25850        self.write_space();
25851        if let Some(materialized) = e.materialized {
25852            if materialized {
25853                self.write_keyword("MATERIALIZED");
25854            } else {
25855                self.write_keyword("NOT MATERIALIZED");
25856            }
25857            self.write_space();
25858        }
25859        self.write("(");
25860        self.generate_expression(&e.this)?;
25861        self.write(")");
25862        Ok(())
25863    }
25864
25865    fn generate_cube(&mut self, e: &Cube) -> Result<()> {
25866        // Python: return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
25867        if e.expressions.is_empty() {
25868            self.write_keyword("WITH CUBE");
25869        } else {
25870            self.write_keyword("CUBE");
25871            self.write("(");
25872            for (i, expr) in e.expressions.iter().enumerate() {
25873                if i > 0 {
25874                    self.write(", ");
25875                }
25876                self.generate_expression(expr)?;
25877            }
25878            self.write(")");
25879        }
25880        Ok(())
25881    }
25882
25883    fn generate_current_datetime(&mut self, e: &CurrentDatetime) -> Result<()> {
25884        // CURRENT_DATETIME or CURRENT_DATETIME(timezone)
25885        self.write_keyword("CURRENT_DATETIME");
25886        if let Some(this) = &e.this {
25887            self.write("(");
25888            self.generate_expression(this)?;
25889            self.write(")");
25890        }
25891        Ok(())
25892    }
25893
25894    fn generate_current_schema(&mut self, _e: &CurrentSchema) -> Result<()> {
25895        // CURRENT_SCHEMA - no arguments
25896        self.write_keyword("CURRENT_SCHEMA");
25897        Ok(())
25898    }
25899
25900    fn generate_current_schemas(&mut self, e: &CurrentSchemas) -> Result<()> {
25901        // CURRENT_SCHEMAS(include_implicit)
25902        self.write_keyword("CURRENT_SCHEMAS");
25903        self.write("(");
25904        if let Some(this) = &e.this {
25905            self.generate_expression(this)?;
25906        }
25907        self.write(")");
25908        Ok(())
25909    }
25910
25911    fn generate_current_user(&mut self, e: &CurrentUser) -> Result<()> {
25912        // CURRENT_USER or CURRENT_USER()
25913        self.write_keyword("CURRENT_USER");
25914        // Some dialects always need parens: Snowflake, Spark, Hive, DuckDB, BigQuery, MySQL, Databricks
25915        let needs_parens = e.this.is_some()
25916            || matches!(
25917                self.config.dialect,
25918                Some(DialectType::Snowflake)
25919                    | Some(DialectType::Spark)
25920                    | Some(DialectType::Hive)
25921                    | Some(DialectType::DuckDB)
25922                    | Some(DialectType::BigQuery)
25923                    | Some(DialectType::MySQL)
25924                    | Some(DialectType::Databricks)
25925            );
25926        if needs_parens {
25927            self.write("()");
25928        }
25929        Ok(())
25930    }
25931
25932    fn generate_d_pipe(&mut self, e: &DPipe) -> Result<()> {
25933        // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
25934        if self.config.dialect == Some(DialectType::Solr) {
25935            self.generate_expression(&e.this)?;
25936            self.write(" ");
25937            self.write_keyword("OR");
25938            self.write(" ");
25939            self.generate_expression(&e.expression)?;
25940        } else {
25941            // String concatenation: this || expression
25942            self.generate_expression(&e.this)?;
25943            self.write(" || ");
25944            self.generate_expression(&e.expression)?;
25945        }
25946        Ok(())
25947    }
25948
25949    fn generate_data_blocksize_property(&mut self, e: &DataBlocksizeProperty) -> Result<()> {
25950        // DATABLOCKSIZE=... (Teradata)
25951        self.write_keyword("DATABLOCKSIZE");
25952        self.write("=");
25953        if let Some(size) = e.size {
25954            self.write(&size.to_string());
25955            if let Some(units) = &e.units {
25956                self.write_space();
25957                self.generate_expression(units)?;
25958            }
25959        } else if e.minimum.is_some() {
25960            self.write_keyword("MINIMUM");
25961        } else if e.maximum.is_some() {
25962            self.write_keyword("MAXIMUM");
25963        } else if e.default.is_some() {
25964            self.write_keyword("DEFAULT");
25965        }
25966        Ok(())
25967    }
25968
25969    fn generate_data_deletion_property(&mut self, e: &DataDeletionProperty) -> Result<()> {
25970        // DATA_DELETION=ON or DATA_DELETION=OFF or DATA_DELETION=ON(FILTER_COLUMN=col, RETENTION_PERIOD=...)
25971        self.write_keyword("DATA_DELETION");
25972        self.write("=");
25973
25974        let is_on = matches!(&*e.on, Expression::Boolean(BooleanLiteral { value: true }));
25975        let has_options = e.filter_column.is_some() || e.retention_period.is_some();
25976
25977        if is_on {
25978            self.write_keyword("ON");
25979            if has_options {
25980                self.write("(");
25981                let mut first = true;
25982                if let Some(filter_column) = &e.filter_column {
25983                    self.write_keyword("FILTER_COLUMN");
25984                    self.write("=");
25985                    self.generate_expression(filter_column)?;
25986                    first = false;
25987                }
25988                if let Some(retention_period) = &e.retention_period {
25989                    if !first {
25990                        self.write(", ");
25991                    }
25992                    self.write_keyword("RETENTION_PERIOD");
25993                    self.write("=");
25994                    self.generate_expression(retention_period)?;
25995                }
25996                self.write(")");
25997            }
25998        } else {
25999            self.write_keyword("OFF");
26000        }
26001        Ok(())
26002    }
26003
26004    /// Generate a Date function expression
26005    /// For Exasol: {d'value'} -> TO_DATE('value')
26006    /// For other dialects: DATE('value')
26007    fn generate_date_func(&mut self, e: &UnaryFunc) -> Result<()> {
26008        use crate::dialects::DialectType;
26009        use crate::expressions::Literal;
26010
26011        match self.config.dialect {
26012            // Exasol uses TO_DATE for Date expressions
26013            Some(DialectType::Exasol) => {
26014                self.write_keyword("TO_DATE");
26015                self.write("(");
26016                // Extract the string value from the expression if it's a string literal
26017                match &e.this {
26018                    Expression::Literal(Literal::String(s)) => {
26019                        self.write("'");
26020                        self.write(s);
26021                        self.write("'");
26022                    }
26023                    _ => {
26024                        self.generate_expression(&e.this)?;
26025                    }
26026                }
26027                self.write(")");
26028            }
26029            // Standard: DATE(value)
26030            _ => {
26031                self.write_keyword("DATE");
26032                self.write("(");
26033                self.generate_expression(&e.this)?;
26034                self.write(")");
26035            }
26036        }
26037        Ok(())
26038    }
26039
26040    fn generate_date_bin(&mut self, e: &DateBin) -> Result<()> {
26041        // DATE_BIN(interval, timestamp[, origin])
26042        self.write_keyword("DATE_BIN");
26043        self.write("(");
26044        self.generate_expression(&e.this)?;
26045        self.write(", ");
26046        self.generate_expression(&e.expression)?;
26047        if let Some(origin) = &e.origin {
26048            self.write(", ");
26049            self.generate_expression(origin)?;
26050        }
26051        self.write(")");
26052        Ok(())
26053    }
26054
26055    fn generate_date_format_column_constraint(
26056        &mut self,
26057        e: &DateFormatColumnConstraint,
26058    ) -> Result<()> {
26059        // FORMAT 'format_string' (Teradata)
26060        self.write_keyword("FORMAT");
26061        self.write_space();
26062        self.generate_expression(&e.this)?;
26063        Ok(())
26064    }
26065
26066    fn generate_date_from_parts(&mut self, e: &DateFromParts) -> Result<()> {
26067        // DATE_FROM_PARTS(year, month, day) or DATEFROMPARTS(year, month, day)
26068        self.write_keyword("DATE_FROM_PARTS");
26069        self.write("(");
26070        let mut first = true;
26071        if let Some(year) = &e.year {
26072            self.generate_expression(year)?;
26073            first = false;
26074        }
26075        if let Some(month) = &e.month {
26076            if !first {
26077                self.write(", ");
26078            }
26079            self.generate_expression(month)?;
26080            first = false;
26081        }
26082        if let Some(day) = &e.day {
26083            if !first {
26084                self.write(", ");
26085            }
26086            self.generate_expression(day)?;
26087        }
26088        self.write(")");
26089        Ok(())
26090    }
26091
26092    fn generate_datetime(&mut self, e: &Datetime) -> Result<()> {
26093        // DATETIME(this) or DATETIME(this, expression)
26094        self.write_keyword("DATETIME");
26095        self.write("(");
26096        self.generate_expression(&e.this)?;
26097        if let Some(expr) = &e.expression {
26098            self.write(", ");
26099            self.generate_expression(expr)?;
26100        }
26101        self.write(")");
26102        Ok(())
26103    }
26104
26105    fn generate_datetime_add(&mut self, e: &DatetimeAdd) -> Result<()> {
26106        // DATETIME_ADD(this, expression, unit)
26107        self.write_keyword("DATETIME_ADD");
26108        self.write("(");
26109        self.generate_expression(&e.this)?;
26110        self.write(", ");
26111        self.generate_expression(&e.expression)?;
26112        if let Some(unit) = &e.unit {
26113            self.write(", ");
26114            self.write_keyword(unit);
26115        }
26116        self.write(")");
26117        Ok(())
26118    }
26119
26120    fn generate_datetime_diff(&mut self, e: &DatetimeDiff) -> Result<()> {
26121        // DATETIME_DIFF(this, expression, unit)
26122        self.write_keyword("DATETIME_DIFF");
26123        self.write("(");
26124        self.generate_expression(&e.this)?;
26125        self.write(", ");
26126        self.generate_expression(&e.expression)?;
26127        if let Some(unit) = &e.unit {
26128            self.write(", ");
26129            self.write_keyword(unit);
26130        }
26131        self.write(")");
26132        Ok(())
26133    }
26134
26135    fn generate_datetime_sub(&mut self, e: &DatetimeSub) -> Result<()> {
26136        // DATETIME_SUB(this, expression, unit)
26137        self.write_keyword("DATETIME_SUB");
26138        self.write("(");
26139        self.generate_expression(&e.this)?;
26140        self.write(", ");
26141        self.generate_expression(&e.expression)?;
26142        if let Some(unit) = &e.unit {
26143            self.write(", ");
26144            self.write_keyword(unit);
26145        }
26146        self.write(")");
26147        Ok(())
26148    }
26149
26150    fn generate_datetime_trunc(&mut self, e: &DatetimeTrunc) -> Result<()> {
26151        // DATETIME_TRUNC(this, unit, zone)
26152        self.write_keyword("DATETIME_TRUNC");
26153        self.write("(");
26154        self.generate_expression(&e.this)?;
26155        self.write(", ");
26156        self.write_keyword(&e.unit);
26157        if let Some(zone) = &e.zone {
26158            self.write(", ");
26159            self.generate_expression(zone)?;
26160        }
26161        self.write(")");
26162        Ok(())
26163    }
26164
26165    fn generate_dayname(&mut self, e: &Dayname) -> Result<()> {
26166        // DAYNAME(this)
26167        self.write_keyword("DAYNAME");
26168        self.write("(");
26169        self.generate_expression(&e.this)?;
26170        self.write(")");
26171        Ok(())
26172    }
26173
26174    fn generate_declare(&mut self, e: &Declare) -> Result<()> {
26175        // DECLARE var1 AS type1, var2 AS type2, ...
26176        self.write_keyword("DECLARE");
26177        self.write_space();
26178        for (i, expr) in e.expressions.iter().enumerate() {
26179            if i > 0 {
26180                self.write(", ");
26181            }
26182            self.generate_expression(expr)?;
26183        }
26184        Ok(())
26185    }
26186
26187    fn generate_declare_item(&mut self, e: &DeclareItem) -> Result<()> {
26188        use crate::dialects::DialectType;
26189
26190        // variable TYPE [DEFAULT default]
26191        self.generate_expression(&e.this)?;
26192        // BigQuery multi-variable: DECLARE X, Y, Z INT64
26193        for name in &e.additional_names {
26194            self.write(", ");
26195            self.generate_expression(name)?;
26196        }
26197        if let Some(kind) = &e.kind {
26198            self.write_space();
26199            // BigQuery uses: DECLARE x INT64 DEFAULT value (no AS)
26200            // TSQL: Always includes AS (normalization)
26201            // Others: Include AS if present in original
26202            match self.config.dialect {
26203                Some(DialectType::BigQuery) => {
26204                    self.write(kind);
26205                }
26206                Some(DialectType::TSQL) => {
26207                    // TSQL: Check for complex TABLE constraints that should be passed through unchanged
26208                    // Python sqlglot falls back to Command for TABLE declarations with CLUSTERED,
26209                    // NONCLUSTERED, or INDEX constraints
26210                    let is_complex_table = kind.starts_with("TABLE")
26211                        && (kind.contains("CLUSTERED") || kind.contains("INDEX"));
26212
26213                    if is_complex_table {
26214                        // Complex TABLE declarations: preserve as-is (no AS, no INT normalization)
26215                        self.write(kind);
26216                    } else {
26217                        // Simple declarations: add AS (except for CURSOR) and normalize INT
26218                        if !kind.starts_with("CURSOR") {
26219                            self.write_keyword("AS");
26220                            self.write_space();
26221                        }
26222                        // Normalize INT to INTEGER for TSQL DECLARE statements
26223                        if kind == "INT" {
26224                            self.write("INTEGER");
26225                        } else if kind.starts_with("TABLE") {
26226                            // Normalize INT to INTEGER inside TABLE column definitions
26227                            let normalized = kind
26228                                .replace(" INT ", " INTEGER ")
26229                                .replace(" INT,", " INTEGER,")
26230                                .replace(" INT)", " INTEGER)")
26231                                .replace("(INT ", "(INTEGER ");
26232                            self.write(&normalized);
26233                        } else {
26234                            self.write(kind);
26235                        }
26236                    }
26237                }
26238                _ => {
26239                    if e.has_as {
26240                        self.write_keyword("AS");
26241                        self.write_space();
26242                    }
26243                    self.write(kind);
26244                }
26245            }
26246        }
26247        if let Some(default) = &e.default {
26248            // BigQuery uses DEFAULT, others use =
26249            match self.config.dialect {
26250                Some(DialectType::BigQuery) => {
26251                    self.write_space();
26252                    self.write_keyword("DEFAULT");
26253                    self.write_space();
26254                }
26255                _ => {
26256                    self.write(" = ");
26257                }
26258            }
26259            self.generate_expression(default)?;
26260        }
26261        Ok(())
26262    }
26263
26264    fn generate_decode_case(&mut self, e: &DecodeCase) -> Result<()> {
26265        // DECODE(expr, search1, result1, search2, result2, ..., default)
26266        self.write_keyword("DECODE");
26267        self.write("(");
26268        for (i, expr) in e.expressions.iter().enumerate() {
26269            if i > 0 {
26270                self.write(", ");
26271            }
26272            self.generate_expression(expr)?;
26273        }
26274        self.write(")");
26275        Ok(())
26276    }
26277
26278    fn generate_decompress_binary(&mut self, e: &DecompressBinary) -> Result<()> {
26279        // DECOMPRESS(expr, 'method')
26280        self.write_keyword("DECOMPRESS");
26281        self.write("(");
26282        self.generate_expression(&e.this)?;
26283        self.write(", '");
26284        self.write(&e.method);
26285        self.write("')");
26286        Ok(())
26287    }
26288
26289    fn generate_decompress_string(&mut self, e: &DecompressString) -> Result<()> {
26290        // DECOMPRESS(expr, 'method')
26291        self.write_keyword("DECOMPRESS");
26292        self.write("(");
26293        self.generate_expression(&e.this)?;
26294        self.write(", '");
26295        self.write(&e.method);
26296        self.write("')");
26297        Ok(())
26298    }
26299
26300    fn generate_decrypt(&mut self, e: &Decrypt) -> Result<()> {
26301        // DECRYPT(value, passphrase [, aad [, algorithm]])
26302        self.write_keyword("DECRYPT");
26303        self.write("(");
26304        self.generate_expression(&e.this)?;
26305        if let Some(passphrase) = &e.passphrase {
26306            self.write(", ");
26307            self.generate_expression(passphrase)?;
26308        }
26309        if let Some(aad) = &e.aad {
26310            self.write(", ");
26311            self.generate_expression(aad)?;
26312        }
26313        if let Some(method) = &e.encryption_method {
26314            self.write(", ");
26315            self.generate_expression(method)?;
26316        }
26317        self.write(")");
26318        Ok(())
26319    }
26320
26321    fn generate_decrypt_raw(&mut self, e: &DecryptRaw) -> Result<()> {
26322        // DECRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
26323        self.write_keyword("DECRYPT_RAW");
26324        self.write("(");
26325        self.generate_expression(&e.this)?;
26326        if let Some(key) = &e.key {
26327            self.write(", ");
26328            self.generate_expression(key)?;
26329        }
26330        if let Some(iv) = &e.iv {
26331            self.write(", ");
26332            self.generate_expression(iv)?;
26333        }
26334        if let Some(aad) = &e.aad {
26335            self.write(", ");
26336            self.generate_expression(aad)?;
26337        }
26338        if let Some(method) = &e.encryption_method {
26339            self.write(", ");
26340            self.generate_expression(method)?;
26341        }
26342        self.write(")");
26343        Ok(())
26344    }
26345
26346    fn generate_definer_property(&mut self, e: &DefinerProperty) -> Result<()> {
26347        // DEFINER = user
26348        self.write_keyword("DEFINER");
26349        self.write(" = ");
26350        self.generate_expression(&e.this)?;
26351        Ok(())
26352    }
26353
26354    fn generate_detach(&mut self, e: &Detach) -> Result<()> {
26355        // Python: DETACH[DATABASE IF EXISTS] this
26356        self.write_keyword("DETACH");
26357        if e.exists {
26358            self.write_keyword(" DATABASE IF EXISTS");
26359        }
26360        self.write_space();
26361        self.generate_expression(&e.this)?;
26362        Ok(())
26363    }
26364
26365    fn generate_dict_property(&mut self, e: &DictProperty) -> Result<()> {
26366        let property_name = match e.this.as_ref() {
26367            Expression::Identifier(id) => id.name.as_str(),
26368            Expression::Var(v) => v.this.as_str(),
26369            _ => "DICTIONARY",
26370        };
26371        self.write_keyword(property_name);
26372        self.write("(");
26373        self.write(&e.kind);
26374        if let Some(settings) = &e.settings {
26375            self.write("(");
26376            if let Expression::Tuple(t) = settings.as_ref() {
26377                if self.config.pretty && !t.expressions.is_empty() {
26378                    self.write_newline();
26379                    self.indent_level += 1;
26380                    for (i, pair) in t.expressions.iter().enumerate() {
26381                        if i > 0 {
26382                            self.write(",");
26383                            self.write_newline();
26384                        }
26385                        self.write_indent();
26386                        if let Expression::Tuple(pair_tuple) = pair {
26387                            if let Some(k) = pair_tuple.expressions.first() {
26388                                self.generate_expression(k)?;
26389                            }
26390                            if let Some(v) = pair_tuple.expressions.get(1) {
26391                                self.write(" ");
26392                                self.generate_expression(v)?;
26393                            }
26394                        } else {
26395                            self.generate_expression(pair)?;
26396                        }
26397                    }
26398                    self.indent_level -= 1;
26399                    self.write_newline();
26400                    self.write_indent();
26401                } else {
26402                    for (i, pair) in t.expressions.iter().enumerate() {
26403                        if i > 0 {
26404                            self.write(", ");
26405                        }
26406                        if let Expression::Tuple(pair_tuple) = pair {
26407                            if let Some(k) = pair_tuple.expressions.first() {
26408                                self.generate_expression(k)?;
26409                            }
26410                            if let Some(v) = pair_tuple.expressions.get(1) {
26411                                self.write(" ");
26412                                self.generate_expression(v)?;
26413                            }
26414                        } else {
26415                            self.generate_expression(pair)?;
26416                        }
26417                    }
26418                }
26419            } else {
26420                self.generate_expression(settings)?;
26421            }
26422            self.write(")");
26423        } else if property_name.eq_ignore_ascii_case("LAYOUT") {
26424            self.write("()");
26425        }
26426        self.write(")");
26427        Ok(())
26428    }
26429
26430    fn generate_dict_range(&mut self, e: &DictRange) -> Result<()> {
26431        let property_name = match e.this.as_ref() {
26432            Expression::Identifier(id) => id.name.as_str(),
26433            Expression::Var(v) => v.this.as_str(),
26434            _ => "RANGE",
26435        };
26436        self.write_keyword(property_name);
26437        self.write("(");
26438        if let Some(min) = &e.min {
26439            self.write_keyword("MIN");
26440            self.write_space();
26441            self.generate_expression(min)?;
26442        }
26443        if let Some(max) = &e.max {
26444            self.write_space();
26445            self.write_keyword("MAX");
26446            self.write_space();
26447            self.generate_expression(max)?;
26448        }
26449        self.write(")");
26450        Ok(())
26451    }
26452
26453    fn generate_directory(&mut self, e: &Directory) -> Result<()> {
26454        // Python: {local}DIRECTORY {this}{row_format}
26455        if e.local.is_some() {
26456            self.write_keyword("LOCAL ");
26457        }
26458        self.write_keyword("DIRECTORY");
26459        self.write_space();
26460        self.generate_expression(&e.this)?;
26461        if let Some(row_format) = &e.row_format {
26462            self.write_space();
26463            self.generate_expression(row_format)?;
26464        }
26465        Ok(())
26466    }
26467
26468    fn generate_dist_key_property(&mut self, e: &DistKeyProperty) -> Result<()> {
26469        // Redshift: DISTKEY(column)
26470        self.write_keyword("DISTKEY");
26471        self.write("(");
26472        self.generate_expression(&e.this)?;
26473        self.write(")");
26474        Ok(())
26475    }
26476
26477    fn generate_dist_style_property(&mut self, e: &DistStyleProperty) -> Result<()> {
26478        // Redshift: DISTSTYLE KEY|ALL|EVEN|AUTO
26479        self.write_keyword("DISTSTYLE");
26480        self.write_space();
26481        self.generate_expression(&e.this)?;
26482        Ok(())
26483    }
26484
26485    fn generate_distribute_by(&mut self, e: &DistributeBy) -> Result<()> {
26486        // Python: "DISTRIBUTE BY" expressions
26487        self.write_keyword("DISTRIBUTE BY");
26488        self.write_space();
26489        for (i, expr) in e.expressions.iter().enumerate() {
26490            if i > 0 {
26491                self.write(", ");
26492            }
26493            self.generate_expression(expr)?;
26494        }
26495        Ok(())
26496    }
26497
26498    fn generate_distributed_by_property(&mut self, e: &DistributedByProperty) -> Result<()> {
26499        // Python: DISTRIBUTED BY kind (expressions) BUCKETS buckets order
26500        self.write_keyword("DISTRIBUTED BY");
26501        self.write_space();
26502        self.write(&e.kind);
26503        if !e.expressions.is_empty() {
26504            self.write(" (");
26505            for (i, expr) in e.expressions.iter().enumerate() {
26506                if i > 0 {
26507                    self.write(", ");
26508                }
26509                self.generate_expression(expr)?;
26510            }
26511            self.write(")");
26512        }
26513        if let Some(buckets) = &e.buckets {
26514            self.write_space();
26515            self.write_keyword("BUCKETS");
26516            self.write_space();
26517            self.generate_expression(buckets)?;
26518        }
26519        if let Some(order) = &e.order {
26520            self.write_space();
26521            self.generate_expression(order)?;
26522        }
26523        Ok(())
26524    }
26525
26526    fn generate_dot_product(&mut self, e: &DotProduct) -> Result<()> {
26527        // DOT_PRODUCT(vector1, vector2)
26528        self.write_keyword("DOT_PRODUCT");
26529        self.write("(");
26530        self.generate_expression(&e.this)?;
26531        self.write(", ");
26532        self.generate_expression(&e.expression)?;
26533        self.write(")");
26534        Ok(())
26535    }
26536
26537    fn generate_drop_partition(&mut self, e: &DropPartition) -> Result<()> {
26538        // Python: DROP{IF EXISTS }expressions
26539        self.write_keyword("DROP");
26540        if e.exists {
26541            self.write_keyword(" IF EXISTS ");
26542        } else {
26543            self.write_space();
26544        }
26545        for (i, expr) in e.expressions.iter().enumerate() {
26546            if i > 0 {
26547                self.write(", ");
26548            }
26549            self.generate_expression(expr)?;
26550        }
26551        Ok(())
26552    }
26553
26554    fn generate_duplicate_key_property(&mut self, e: &DuplicateKeyProperty) -> Result<()> {
26555        // Python: DUPLICATE KEY (expressions)
26556        self.write_keyword("DUPLICATE KEY");
26557        self.write(" (");
26558        for (i, expr) in e.expressions.iter().enumerate() {
26559            if i > 0 {
26560                self.write(", ");
26561            }
26562            self.generate_expression(expr)?;
26563        }
26564        self.write(")");
26565        Ok(())
26566    }
26567
26568    fn generate_elt(&mut self, e: &Elt) -> Result<()> {
26569        // ELT(index, str1, str2, ...)
26570        self.write_keyword("ELT");
26571        self.write("(");
26572        self.generate_expression(&e.this)?;
26573        for expr in &e.expressions {
26574            self.write(", ");
26575            self.generate_expression(expr)?;
26576        }
26577        self.write(")");
26578        Ok(())
26579    }
26580
26581    fn generate_encode(&mut self, e: &Encode) -> Result<()> {
26582        // ENCODE(string, charset)
26583        self.write_keyword("ENCODE");
26584        self.write("(");
26585        self.generate_expression(&e.this)?;
26586        if let Some(charset) = &e.charset {
26587            self.write(", ");
26588            self.generate_expression(charset)?;
26589        }
26590        self.write(")");
26591        Ok(())
26592    }
26593
26594    fn generate_encode_property(&mut self, e: &EncodeProperty) -> Result<()> {
26595        // Python: [KEY ]ENCODE this [properties]
26596        if e.key.is_some() {
26597            self.write_keyword("KEY ");
26598        }
26599        self.write_keyword("ENCODE");
26600        self.write_space();
26601        self.generate_expression(&e.this)?;
26602        if !e.properties.is_empty() {
26603            self.write(" (");
26604            for (i, prop) in e.properties.iter().enumerate() {
26605                if i > 0 {
26606                    self.write(", ");
26607                }
26608                self.generate_expression(prop)?;
26609            }
26610            self.write(")");
26611        }
26612        Ok(())
26613    }
26614
26615    fn generate_encrypt(&mut self, e: &Encrypt) -> Result<()> {
26616        // ENCRYPT(value, passphrase [, aad [, algorithm]])
26617        self.write_keyword("ENCRYPT");
26618        self.write("(");
26619        self.generate_expression(&e.this)?;
26620        if let Some(passphrase) = &e.passphrase {
26621            self.write(", ");
26622            self.generate_expression(passphrase)?;
26623        }
26624        if let Some(aad) = &e.aad {
26625            self.write(", ");
26626            self.generate_expression(aad)?;
26627        }
26628        if let Some(method) = &e.encryption_method {
26629            self.write(", ");
26630            self.generate_expression(method)?;
26631        }
26632        self.write(")");
26633        Ok(())
26634    }
26635
26636    fn generate_encrypt_raw(&mut self, e: &EncryptRaw) -> Result<()> {
26637        // ENCRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
26638        self.write_keyword("ENCRYPT_RAW");
26639        self.write("(");
26640        self.generate_expression(&e.this)?;
26641        if let Some(key) = &e.key {
26642            self.write(", ");
26643            self.generate_expression(key)?;
26644        }
26645        if let Some(iv) = &e.iv {
26646            self.write(", ");
26647            self.generate_expression(iv)?;
26648        }
26649        if let Some(aad) = &e.aad {
26650            self.write(", ");
26651            self.generate_expression(aad)?;
26652        }
26653        if let Some(method) = &e.encryption_method {
26654            self.write(", ");
26655            self.generate_expression(method)?;
26656        }
26657        self.write(")");
26658        Ok(())
26659    }
26660
26661    fn generate_engine_property(&mut self, e: &EngineProperty) -> Result<()> {
26662        // MySQL: ENGINE = InnoDB
26663        self.write_keyword("ENGINE");
26664        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
26665            self.write("=");
26666        } else {
26667            self.write(" = ");
26668        }
26669        self.generate_expression(&e.this)?;
26670        Ok(())
26671    }
26672
26673    fn generate_enviroment_property(&mut self, e: &EnviromentProperty) -> Result<()> {
26674        // ENVIRONMENT (expressions)
26675        self.write_keyword("ENVIRONMENT");
26676        self.write(" (");
26677        for (i, expr) in e.expressions.iter().enumerate() {
26678            if i > 0 {
26679                self.write(", ");
26680            }
26681            self.generate_expression(expr)?;
26682        }
26683        self.write(")");
26684        Ok(())
26685    }
26686
26687    fn generate_ephemeral_column_constraint(
26688        &mut self,
26689        e: &EphemeralColumnConstraint,
26690    ) -> Result<()> {
26691        // MySQL: EPHEMERAL [expr]
26692        self.write_keyword("EPHEMERAL");
26693        if let Some(this) = &e.this {
26694            self.write_space();
26695            self.generate_expression(this)?;
26696        }
26697        Ok(())
26698    }
26699
26700    fn generate_equal_null(&mut self, e: &EqualNull) -> Result<()> {
26701        // Snowflake: EQUAL_NULL(a, b)
26702        self.write_keyword("EQUAL_NULL");
26703        self.write("(");
26704        self.generate_expression(&e.this)?;
26705        self.write(", ");
26706        self.generate_expression(&e.expression)?;
26707        self.write(")");
26708        Ok(())
26709    }
26710
26711    fn generate_euclidean_distance(&mut self, e: &EuclideanDistance) -> Result<()> {
26712        use crate::dialects::DialectType;
26713
26714        // PostgreSQL uses <-> operator syntax
26715        match self.config.dialect {
26716            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
26717                self.generate_expression(&e.this)?;
26718                self.write(" <-> ");
26719                self.generate_expression(&e.expression)?;
26720            }
26721            _ => {
26722                // Other dialects use EUCLIDEAN_DISTANCE function
26723                self.write_keyword("EUCLIDEAN_DISTANCE");
26724                self.write("(");
26725                self.generate_expression(&e.this)?;
26726                self.write(", ");
26727                self.generate_expression(&e.expression)?;
26728                self.write(")");
26729            }
26730        }
26731        Ok(())
26732    }
26733
26734    fn generate_execute_as_property(&mut self, e: &ExecuteAsProperty) -> Result<()> {
26735        // EXECUTE AS CALLER|OWNER|user
26736        self.write_keyword("EXECUTE AS");
26737        self.write_space();
26738        self.generate_expression(&e.this)?;
26739        Ok(())
26740    }
26741
26742    fn generate_export(&mut self, e: &Export) -> Result<()> {
26743        // BigQuery: EXPORT DATA [WITH CONNECTION connection] OPTIONS (...) AS query
26744        self.write_keyword("EXPORT DATA");
26745        if let Some(connection) = &e.connection {
26746            self.write_space();
26747            self.write_keyword("WITH CONNECTION");
26748            self.write_space();
26749            self.generate_expression(connection)?;
26750        }
26751        if !e.options.is_empty() {
26752            self.write_space();
26753            self.generate_options_clause(&e.options)?;
26754        }
26755        self.write_space();
26756        self.write_keyword("AS");
26757        self.write_space();
26758        self.generate_expression(&e.this)?;
26759        Ok(())
26760    }
26761
26762    fn generate_external_property(&mut self, e: &ExternalProperty) -> Result<()> {
26763        // EXTERNAL [this]
26764        self.write_keyword("EXTERNAL");
26765        if let Some(this) = &e.this {
26766            self.write_space();
26767            self.generate_expression(this)?;
26768        }
26769        Ok(())
26770    }
26771
26772    fn generate_fallback_property(&mut self, e: &FallbackProperty) -> Result<()> {
26773        // Python: {no}FALLBACK{protection}
26774        if e.no.is_some() {
26775            self.write_keyword("NO ");
26776        }
26777        self.write_keyword("FALLBACK");
26778        if e.protection.is_some() {
26779            self.write_keyword(" PROTECTION");
26780        }
26781        Ok(())
26782    }
26783
26784    fn generate_farm_fingerprint(&mut self, e: &FarmFingerprint) -> Result<()> {
26785        // BigQuery: FARM_FINGERPRINT(value)
26786        self.write_keyword("FARM_FINGERPRINT");
26787        self.write("(");
26788        for (i, expr) in e.expressions.iter().enumerate() {
26789            if i > 0 {
26790                self.write(", ");
26791            }
26792            self.generate_expression(expr)?;
26793        }
26794        self.write(")");
26795        Ok(())
26796    }
26797
26798    fn generate_features_at_time(&mut self, e: &FeaturesAtTime) -> Result<()> {
26799        // BigQuery ML: FEATURES_AT_TIME(feature_view, time, [num_rows], [ignore_feature_nulls])
26800        self.write_keyword("FEATURES_AT_TIME");
26801        self.write("(");
26802        self.generate_expression(&e.this)?;
26803        if let Some(time) = &e.time {
26804            self.write(", ");
26805            self.generate_expression(time)?;
26806        }
26807        if let Some(num_rows) = &e.num_rows {
26808            self.write(", ");
26809            self.generate_expression(num_rows)?;
26810        }
26811        if let Some(ignore_nulls) = &e.ignore_feature_nulls {
26812            self.write(", ");
26813            self.generate_expression(ignore_nulls)?;
26814        }
26815        self.write(")");
26816        Ok(())
26817    }
26818
26819    fn generate_fetch(&mut self, e: &Fetch) -> Result<()> {
26820        // For dialects that prefer LIMIT, convert simple FETCH to LIMIT
26821        let use_limit = !e.percent
26822            && !e.with_ties
26823            && e.count.is_some()
26824            && matches!(
26825                self.config.dialect,
26826                Some(DialectType::Spark)
26827                    | Some(DialectType::Hive)
26828                    | Some(DialectType::DuckDB)
26829                    | Some(DialectType::SQLite)
26830                    | Some(DialectType::MySQL)
26831                    | Some(DialectType::BigQuery)
26832                    | Some(DialectType::Databricks)
26833                    | Some(DialectType::StarRocks)
26834                    | Some(DialectType::Doris)
26835                    | Some(DialectType::Athena)
26836                    | Some(DialectType::ClickHouse)
26837            );
26838
26839        if use_limit {
26840            self.write_keyword("LIMIT");
26841            self.write_space();
26842            self.generate_expression(e.count.as_ref().unwrap())?;
26843            return Ok(());
26844        }
26845
26846        // Python: FETCH direction count limit_options
26847        self.write_keyword("FETCH");
26848        if !e.direction.is_empty() {
26849            self.write_space();
26850            self.write_keyword(&e.direction);
26851        }
26852        if let Some(count) = &e.count {
26853            self.write_space();
26854            self.generate_expression(count)?;
26855        }
26856        // Generate PERCENT, ROWS, WITH TIES/ONLY
26857        if e.percent {
26858            self.write_keyword(" PERCENT");
26859        }
26860        if e.rows {
26861            self.write_keyword(" ROWS");
26862        }
26863        if e.with_ties {
26864            self.write_keyword(" WITH TIES");
26865        } else if e.rows {
26866            self.write_keyword(" ONLY");
26867        } else {
26868            self.write_keyword(" ROWS ONLY");
26869        }
26870        Ok(())
26871    }
26872
26873    fn generate_file_format_property(&mut self, e: &FileFormatProperty) -> Result<()> {
26874        // For Hive format: STORED AS this or STORED AS INPUTFORMAT x OUTPUTFORMAT y
26875        // For Spark/Databricks without hive_format: USING this
26876        // For Snowflake/others: FILE_FORMAT = this or FILE_FORMAT = (expressions)
26877        if e.hive_format.is_some() {
26878            // Hive format: STORED AS ...
26879            self.write_keyword("STORED AS");
26880            self.write_space();
26881            if let Some(this) = &e.this {
26882                // Uppercase the format name (e.g., parquet -> PARQUET)
26883                if let Expression::Identifier(id) = this.as_ref() {
26884                    self.write_keyword(&id.name.to_uppercase());
26885                } else {
26886                    self.generate_expression(this)?;
26887                }
26888            }
26889        } else if matches!(self.config.dialect, Some(DialectType::Hive)) {
26890            // Hive: STORED AS format
26891            self.write_keyword("STORED AS");
26892            self.write_space();
26893            if let Some(this) = &e.this {
26894                if let Expression::Identifier(id) = this.as_ref() {
26895                    self.write_keyword(&id.name.to_uppercase());
26896                } else {
26897                    self.generate_expression(this)?;
26898                }
26899            }
26900        } else if matches!(
26901            self.config.dialect,
26902            Some(DialectType::Spark) | Some(DialectType::Databricks)
26903        ) {
26904            // Spark/Databricks: USING format (e.g., USING DELTA)
26905            self.write_keyword("USING");
26906            self.write_space();
26907            if let Some(this) = &e.this {
26908                self.generate_expression(this)?;
26909            }
26910        } else {
26911            // Snowflake/standard format
26912            self.write_keyword("FILE_FORMAT");
26913            self.write(" = ");
26914            if let Some(this) = &e.this {
26915                self.generate_expression(this)?;
26916            } else if !e.expressions.is_empty() {
26917                self.write("(");
26918                for (i, expr) in e.expressions.iter().enumerate() {
26919                    if i > 0 {
26920                        self.write(", ");
26921                    }
26922                    self.generate_expression(expr)?;
26923                }
26924                self.write(")");
26925            }
26926        }
26927        Ok(())
26928    }
26929
26930    fn generate_filter(&mut self, e: &Filter) -> Result<()> {
26931        // agg_func FILTER(WHERE condition)
26932        self.generate_expression(&e.this)?;
26933        self.write_space();
26934        self.write_keyword("FILTER");
26935        self.write("(");
26936        self.write_keyword("WHERE");
26937        self.write_space();
26938        self.generate_expression(&e.expression)?;
26939        self.write(")");
26940        Ok(())
26941    }
26942
26943    fn generate_float64(&mut self, e: &Float64) -> Result<()> {
26944        // FLOAT64(this) or FLOAT64(this, expression)
26945        self.write_keyword("FLOAT64");
26946        self.write("(");
26947        self.generate_expression(&e.this)?;
26948        if let Some(expr) = &e.expression {
26949            self.write(", ");
26950            self.generate_expression(expr)?;
26951        }
26952        self.write(")");
26953        Ok(())
26954    }
26955
26956    fn generate_for_in(&mut self, e: &ForIn) -> Result<()> {
26957        // FOR this DO expression
26958        self.write_keyword("FOR");
26959        self.write_space();
26960        self.generate_expression(&e.this)?;
26961        self.write_space();
26962        self.write_keyword("DO");
26963        self.write_space();
26964        self.generate_expression(&e.expression)?;
26965        Ok(())
26966    }
26967
26968    fn generate_foreign_key(&mut self, e: &ForeignKey) -> Result<()> {
26969        // FOREIGN KEY (cols) REFERENCES table(cols) ON DELETE action ON UPDATE action
26970        self.write_keyword("FOREIGN KEY");
26971        if !e.expressions.is_empty() {
26972            self.write(" (");
26973            for (i, expr) in e.expressions.iter().enumerate() {
26974                if i > 0 {
26975                    self.write(", ");
26976                }
26977                self.generate_expression(expr)?;
26978            }
26979            self.write(")");
26980        }
26981        if let Some(reference) = &e.reference {
26982            self.write_space();
26983            self.generate_expression(reference)?;
26984        }
26985        if let Some(delete) = &e.delete {
26986            self.write_space();
26987            self.write_keyword("ON DELETE");
26988            self.write_space();
26989            self.generate_expression(delete)?;
26990        }
26991        if let Some(update) = &e.update {
26992            self.write_space();
26993            self.write_keyword("ON UPDATE");
26994            self.write_space();
26995            self.generate_expression(update)?;
26996        }
26997        if !e.options.is_empty() {
26998            self.write_space();
26999            for (i, opt) in e.options.iter().enumerate() {
27000                if i > 0 {
27001                    self.write_space();
27002                }
27003                self.generate_expression(opt)?;
27004            }
27005        }
27006        Ok(())
27007    }
27008
27009    fn generate_format(&mut self, e: &Format) -> Result<()> {
27010        // FORMAT(this, expressions...)
27011        self.write_keyword("FORMAT");
27012        self.write("(");
27013        self.generate_expression(&e.this)?;
27014        for expr in &e.expressions {
27015            self.write(", ");
27016            self.generate_expression(expr)?;
27017        }
27018        self.write(")");
27019        Ok(())
27020    }
27021
27022    fn generate_format_phrase(&mut self, e: &FormatPhrase) -> Result<()> {
27023        // Teradata: column (FORMAT 'format_string')
27024        self.generate_expression(&e.this)?;
27025        self.write(" (");
27026        self.write_keyword("FORMAT");
27027        self.write(" '");
27028        self.write(&e.format);
27029        self.write("')");
27030        Ok(())
27031    }
27032
27033    fn generate_freespace_property(&mut self, e: &FreespaceProperty) -> Result<()> {
27034        // Python: FREESPACE=this[PERCENT]
27035        self.write_keyword("FREESPACE");
27036        self.write("=");
27037        self.generate_expression(&e.this)?;
27038        if e.percent.is_some() {
27039            self.write_keyword(" PERCENT");
27040        }
27041        Ok(())
27042    }
27043
27044    fn generate_from(&mut self, e: &From) -> Result<()> {
27045        // Python: return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
27046        self.write_keyword("FROM");
27047        self.write_space();
27048
27049        // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax
27050        // But keep commas when TABLESAMPLE is present
27051        // Also keep commas when the source dialect is Generic/None and target is one of these dialects
27052        use crate::dialects::DialectType;
27053        let has_tablesample = e
27054            .expressions
27055            .iter()
27056            .any(|expr| matches!(expr, Expression::TableSample(_)));
27057        let is_cross_join_dialect = matches!(
27058            self.config.dialect,
27059            Some(DialectType::BigQuery)
27060                | Some(DialectType::Hive)
27061                | Some(DialectType::Spark)
27062                | Some(DialectType::Databricks)
27063                | Some(DialectType::SQLite)
27064                | Some(DialectType::ClickHouse)
27065        );
27066        let source_is_same_as_target2 = self.config.source_dialect.is_some()
27067            && self.config.source_dialect == self.config.dialect;
27068        let source_is_cross_join_dialect2 = matches!(
27069            self.config.source_dialect,
27070            Some(DialectType::BigQuery)
27071                | Some(DialectType::Hive)
27072                | Some(DialectType::Spark)
27073                | Some(DialectType::Databricks)
27074                | Some(DialectType::SQLite)
27075                | Some(DialectType::ClickHouse)
27076        );
27077        let use_cross_join = !has_tablesample
27078            && is_cross_join_dialect
27079            && (source_is_same_as_target2
27080                || source_is_cross_join_dialect2
27081                || self.config.source_dialect.is_none());
27082
27083        // Snowflake wraps standalone VALUES in FROM clause with parentheses
27084        let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
27085
27086        for (i, expr) in e.expressions.iter().enumerate() {
27087            if i > 0 {
27088                if use_cross_join {
27089                    self.write(" CROSS JOIN ");
27090                } else {
27091                    self.write(", ");
27092                }
27093            }
27094            if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
27095                self.write("(");
27096                self.generate_expression(expr)?;
27097                self.write(")");
27098            } else {
27099                self.generate_expression(expr)?;
27100            }
27101        }
27102        Ok(())
27103    }
27104
27105    fn generate_from_base(&mut self, e: &FromBase) -> Result<()> {
27106        // FROM_BASE(this, expression) - convert from base N
27107        self.write_keyword("FROM_BASE");
27108        self.write("(");
27109        self.generate_expression(&e.this)?;
27110        self.write(", ");
27111        self.generate_expression(&e.expression)?;
27112        self.write(")");
27113        Ok(())
27114    }
27115
27116    fn generate_from_time_zone(&mut self, e: &FromTimeZone) -> Result<()> {
27117        // this AT TIME ZONE zone AT TIME ZONE 'UTC'
27118        self.generate_expression(&e.this)?;
27119        if let Some(zone) = &e.zone {
27120            self.write_space();
27121            self.write_keyword("AT TIME ZONE");
27122            self.write_space();
27123            self.generate_expression(zone)?;
27124            self.write_space();
27125            self.write_keyword("AT TIME ZONE");
27126            self.write(" 'UTC'");
27127        }
27128        Ok(())
27129    }
27130
27131    fn generate_gap_fill(&mut self, e: &GapFill) -> Result<()> {
27132        // GAP_FILL(this, ts_column, bucket_width, ...)
27133        self.write_keyword("GAP_FILL");
27134        self.write("(");
27135        self.generate_expression(&e.this)?;
27136        if let Some(ts_column) = &e.ts_column {
27137            self.write(", ");
27138            self.generate_expression(ts_column)?;
27139        }
27140        if let Some(bucket_width) = &e.bucket_width {
27141            self.write(", ");
27142            self.generate_expression(bucket_width)?;
27143        }
27144        if let Some(partitioning_columns) = &e.partitioning_columns {
27145            self.write(", ");
27146            self.generate_expression(partitioning_columns)?;
27147        }
27148        if let Some(value_columns) = &e.value_columns {
27149            self.write(", ");
27150            self.generate_expression(value_columns)?;
27151        }
27152        self.write(")");
27153        Ok(())
27154    }
27155
27156    fn generate_generate_date_array(&mut self, e: &GenerateDateArray) -> Result<()> {
27157        // GENERATE_DATE_ARRAY(start, end, step)
27158        self.write_keyword("GENERATE_DATE_ARRAY");
27159        self.write("(");
27160        let mut first = true;
27161        if let Some(start) = &e.start {
27162            self.generate_expression(start)?;
27163            first = false;
27164        }
27165        if let Some(end) = &e.end {
27166            if !first {
27167                self.write(", ");
27168            }
27169            self.generate_expression(end)?;
27170            first = false;
27171        }
27172        if let Some(step) = &e.step {
27173            if !first {
27174                self.write(", ");
27175            }
27176            self.generate_expression(step)?;
27177        }
27178        self.write(")");
27179        Ok(())
27180    }
27181
27182    fn generate_generate_embedding(&mut self, e: &GenerateEmbedding) -> Result<()> {
27183        // ML.GENERATE_EMBEDDING(model, content, params)
27184        self.write_keyword("ML.GENERATE_EMBEDDING");
27185        self.write("(");
27186        self.generate_expression(&e.this)?;
27187        self.write(", ");
27188        self.generate_expression(&e.expression)?;
27189        if let Some(params) = &e.params_struct {
27190            self.write(", ");
27191            self.generate_expression(params)?;
27192        }
27193        self.write(")");
27194        Ok(())
27195    }
27196
27197    fn generate_generate_series(&mut self, e: &GenerateSeries) -> Result<()> {
27198        // Dialect-specific function name
27199        let fn_name = match self.config.dialect {
27200            Some(DialectType::Presto)
27201            | Some(DialectType::Trino)
27202            | Some(DialectType::Athena)
27203            | Some(DialectType::Spark)
27204            | Some(DialectType::Databricks)
27205            | Some(DialectType::Hive) => "SEQUENCE",
27206            _ => "GENERATE_SERIES",
27207        };
27208        self.write_keyword(fn_name);
27209        self.write("(");
27210        let mut first = true;
27211        if let Some(start) = &e.start {
27212            self.generate_expression(start)?;
27213            first = false;
27214        }
27215        if let Some(end) = &e.end {
27216            if !first {
27217                self.write(", ");
27218            }
27219            self.generate_expression(end)?;
27220            first = false;
27221        }
27222        if let Some(step) = &e.step {
27223            if !first {
27224                self.write(", ");
27225            }
27226            // For Presto/Trino: convert WEEK intervals to DAY multiples
27227            // e.g., INTERVAL '1' WEEK -> (1 * INTERVAL '7' DAY)
27228            if matches!(
27229                self.config.dialect,
27230                Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
27231            ) {
27232                if let Some(converted) = self.convert_week_interval_to_day(step) {
27233                    self.generate_expression(&converted)?;
27234                } else {
27235                    self.generate_expression(step)?;
27236                }
27237            } else {
27238                self.generate_expression(step)?;
27239            }
27240        }
27241        self.write(")");
27242        Ok(())
27243    }
27244
27245    /// Convert a WEEK interval to a DAY-based multiplication expression for Presto/Trino.
27246    /// INTERVAL N WEEK -> (N * INTERVAL '7' DAY)
27247    fn convert_week_interval_to_day(&self, expr: &Expression) -> Option<Expression> {
27248        use crate::expressions::*;
27249        if let Expression::Interval(ref iv) = expr {
27250            // Check for structured WEEK unit
27251            let (is_week, count_str) = if let Some(IntervalUnitSpec::Simple {
27252                unit: IntervalUnit::Week,
27253                ..
27254            }) = &iv.unit
27255            {
27256                // Value is in iv.this
27257                let count = match &iv.this {
27258                    Some(Expression::Literal(Literal::String(s))) => s.clone(),
27259                    Some(Expression::Literal(Literal::Number(s))) => s.clone(),
27260                    _ => return None,
27261                };
27262                (true, count)
27263            } else if iv.unit.is_none() {
27264                // Check for string-encoded interval like "1 WEEK"
27265                if let Some(Expression::Literal(Literal::String(s))) = &iv.this {
27266                    let parts: Vec<&str> = s.trim().splitn(2, char::is_whitespace).collect();
27267                    if parts.len() == 2 && parts[1].eq_ignore_ascii_case("WEEK") {
27268                        (true, parts[0].to_string())
27269                    } else {
27270                        (false, String::new())
27271                    }
27272                } else {
27273                    (false, String::new())
27274                }
27275            } else {
27276                (false, String::new())
27277            };
27278
27279            if is_week {
27280                // Build: (N * INTERVAL '7' DAY)
27281                let count_expr = Expression::Literal(Literal::Number(count_str));
27282                let day_interval = Expression::Interval(Box::new(Interval {
27283                    this: Some(Expression::Literal(Literal::String("7".to_string()))),
27284                    unit: Some(IntervalUnitSpec::Simple {
27285                        unit: IntervalUnit::Day,
27286                        use_plural: false,
27287                    }),
27288                }));
27289                let mul = Expression::Mul(Box::new(BinaryOp {
27290                    left: count_expr,
27291                    right: day_interval,
27292                    left_comments: vec![],
27293                    operator_comments: vec![],
27294                    trailing_comments: vec![],
27295                }));
27296                return Some(Expression::Paren(Box::new(Paren {
27297                    this: mul,
27298                    trailing_comments: vec![],
27299                })));
27300            }
27301        }
27302        None
27303    }
27304
27305    fn generate_generate_timestamp_array(&mut self, e: &GenerateTimestampArray) -> Result<()> {
27306        // GENERATE_TIMESTAMP_ARRAY(start, end, step)
27307        self.write_keyword("GENERATE_TIMESTAMP_ARRAY");
27308        self.write("(");
27309        let mut first = true;
27310        if let Some(start) = &e.start {
27311            self.generate_expression(start)?;
27312            first = false;
27313        }
27314        if let Some(end) = &e.end {
27315            if !first {
27316                self.write(", ");
27317            }
27318            self.generate_expression(end)?;
27319            first = false;
27320        }
27321        if let Some(step) = &e.step {
27322            if !first {
27323                self.write(", ");
27324            }
27325            self.generate_expression(step)?;
27326        }
27327        self.write(")");
27328        Ok(())
27329    }
27330
27331    fn generate_generated_as_identity_column_constraint(
27332        &mut self,
27333        e: &GeneratedAsIdentityColumnConstraint,
27334    ) -> Result<()> {
27335        use crate::dialects::DialectType;
27336
27337        // For Snowflake, use AUTOINCREMENT START x INCREMENT y syntax
27338        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
27339            self.write_keyword("AUTOINCREMENT");
27340            if let Some(start) = &e.start {
27341                self.write_keyword(" START ");
27342                self.generate_expression(start)?;
27343            }
27344            if let Some(increment) = &e.increment {
27345                self.write_keyword(" INCREMENT ");
27346                self.generate_expression(increment)?;
27347            }
27348            return Ok(());
27349        }
27350
27351        // Python: GENERATED [ALWAYS|BY DEFAULT [ON NULL]] AS IDENTITY [(start, increment, ...)]
27352        self.write_keyword("GENERATED");
27353        if let Some(this) = &e.this {
27354            // Check if it's a truthy boolean expression
27355            if let Expression::Boolean(b) = this.as_ref() {
27356                if b.value {
27357                    self.write_keyword(" ALWAYS");
27358                } else {
27359                    self.write_keyword(" BY DEFAULT");
27360                    if e.on_null.is_some() {
27361                        self.write_keyword(" ON NULL");
27362                    }
27363                }
27364            } else {
27365                self.write_keyword(" ALWAYS");
27366            }
27367        }
27368        self.write_keyword(" AS IDENTITY");
27369        // Add sequence options if any
27370        let has_options = e.start.is_some()
27371            || e.increment.is_some()
27372            || e.minvalue.is_some()
27373            || e.maxvalue.is_some();
27374        if has_options {
27375            self.write(" (");
27376            let mut first = true;
27377            if let Some(start) = &e.start {
27378                self.write_keyword("START WITH ");
27379                self.generate_expression(start)?;
27380                first = false;
27381            }
27382            if let Some(increment) = &e.increment {
27383                if !first {
27384                    self.write(" ");
27385                }
27386                self.write_keyword("INCREMENT BY ");
27387                self.generate_expression(increment)?;
27388                first = false;
27389            }
27390            if let Some(minvalue) = &e.minvalue {
27391                if !first {
27392                    self.write(" ");
27393                }
27394                self.write_keyword("MINVALUE ");
27395                self.generate_expression(minvalue)?;
27396                first = false;
27397            }
27398            if let Some(maxvalue) = &e.maxvalue {
27399                if !first {
27400                    self.write(" ");
27401                }
27402                self.write_keyword("MAXVALUE ");
27403                self.generate_expression(maxvalue)?;
27404            }
27405            self.write(")");
27406        }
27407        Ok(())
27408    }
27409
27410    fn generate_generated_as_row_column_constraint(
27411        &mut self,
27412        e: &GeneratedAsRowColumnConstraint,
27413    ) -> Result<()> {
27414        // Python: GENERATED ALWAYS AS ROW START|END [HIDDEN]
27415        self.write_keyword("GENERATED ALWAYS AS ROW ");
27416        if e.start.is_some() {
27417            self.write_keyword("START");
27418        } else {
27419            self.write_keyword("END");
27420        }
27421        if e.hidden.is_some() {
27422            self.write_keyword(" HIDDEN");
27423        }
27424        Ok(())
27425    }
27426
27427    fn generate_get(&mut self, e: &Get) -> Result<()> {
27428        // GET this target properties
27429        self.write_keyword("GET");
27430        self.write_space();
27431        self.generate_expression(&e.this)?;
27432        if let Some(target) = &e.target {
27433            self.write_space();
27434            self.generate_expression(target)?;
27435        }
27436        for prop in &e.properties {
27437            self.write_space();
27438            self.generate_expression(prop)?;
27439        }
27440        Ok(())
27441    }
27442
27443    fn generate_get_extract(&mut self, e: &GetExtract) -> Result<()> {
27444        // GetExtract generates bracket access: this[expression]
27445        self.generate_expression(&e.this)?;
27446        self.write("[");
27447        self.generate_expression(&e.expression)?;
27448        self.write("]");
27449        Ok(())
27450    }
27451
27452    fn generate_getbit(&mut self, e: &Getbit) -> Result<()> {
27453        // GETBIT(this, expression) or GET_BIT(this, expression)
27454        self.write_keyword("GETBIT");
27455        self.write("(");
27456        self.generate_expression(&e.this)?;
27457        self.write(", ");
27458        self.generate_expression(&e.expression)?;
27459        self.write(")");
27460        Ok(())
27461    }
27462
27463    fn generate_grant_principal(&mut self, e: &GrantPrincipal) -> Result<()> {
27464        // [ROLE|GROUP] name (e.g., "ROLE admin", "GROUP qa_users", or just "user1")
27465        if e.is_role {
27466            self.write_keyword("ROLE");
27467            self.write_space();
27468        } else if e.is_group {
27469            self.write_keyword("GROUP");
27470            self.write_space();
27471        }
27472        self.write(&e.name.name);
27473        Ok(())
27474    }
27475
27476    fn generate_grant_privilege(&mut self, e: &GrantPrivilege) -> Result<()> {
27477        // privilege(columns) or just privilege
27478        self.generate_expression(&e.this)?;
27479        if !e.expressions.is_empty() {
27480            self.write("(");
27481            for (i, expr) in e.expressions.iter().enumerate() {
27482                if i > 0 {
27483                    self.write(", ");
27484                }
27485                self.generate_expression(expr)?;
27486            }
27487            self.write(")");
27488        }
27489        Ok(())
27490    }
27491
27492    fn generate_group(&mut self, e: &Group) -> Result<()> {
27493        // Python handles GROUP BY ALL/DISTINCT modifiers and grouping expressions
27494        self.write_keyword("GROUP BY");
27495        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
27496        match e.all {
27497            Some(true) => {
27498                self.write_space();
27499                self.write_keyword("ALL");
27500            }
27501            Some(false) => {
27502                self.write_space();
27503                self.write_keyword("DISTINCT");
27504            }
27505            None => {}
27506        }
27507        if !e.expressions.is_empty() {
27508            self.write_space();
27509            for (i, expr) in e.expressions.iter().enumerate() {
27510                if i > 0 {
27511                    self.write(", ");
27512                }
27513                self.generate_expression(expr)?;
27514            }
27515        }
27516        // Handle CUBE, ROLLUP, GROUPING SETS
27517        if let Some(cube) = &e.cube {
27518            if !e.expressions.is_empty() {
27519                self.write(", ");
27520            } else {
27521                self.write_space();
27522            }
27523            self.generate_expression(cube)?;
27524        }
27525        if let Some(rollup) = &e.rollup {
27526            if !e.expressions.is_empty() || e.cube.is_some() {
27527                self.write(", ");
27528            } else {
27529                self.write_space();
27530            }
27531            self.generate_expression(rollup)?;
27532        }
27533        if let Some(grouping_sets) = &e.grouping_sets {
27534            if !e.expressions.is_empty() || e.cube.is_some() || e.rollup.is_some() {
27535                self.write(", ");
27536            } else {
27537                self.write_space();
27538            }
27539            self.generate_expression(grouping_sets)?;
27540        }
27541        if let Some(totals) = &e.totals {
27542            self.write_space();
27543            self.write_keyword("WITH TOTALS");
27544            self.generate_expression(totals)?;
27545        }
27546        Ok(())
27547    }
27548
27549    fn generate_group_by(&mut self, e: &GroupBy) -> Result<()> {
27550        // GROUP BY expressions
27551        self.write_keyword("GROUP BY");
27552        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
27553        match e.all {
27554            Some(true) => {
27555                self.write_space();
27556                self.write_keyword("ALL");
27557            }
27558            Some(false) => {
27559                self.write_space();
27560                self.write_keyword("DISTINCT");
27561            }
27562            None => {}
27563        }
27564
27565        // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
27566        // These are represented as Cube/Rollup expressions with empty expressions at the end
27567        let mut trailing_cube = false;
27568        let mut trailing_rollup = false;
27569        let mut regular_expressions: Vec<&Expression> = Vec::new();
27570
27571        for expr in &e.expressions {
27572            match expr {
27573                Expression::Cube(c) if c.expressions.is_empty() => {
27574                    trailing_cube = true;
27575                }
27576                Expression::Rollup(r) if r.expressions.is_empty() => {
27577                    trailing_rollup = true;
27578                }
27579                _ => {
27580                    regular_expressions.push(expr);
27581                }
27582            }
27583        }
27584
27585        // In pretty mode, put columns on separate lines
27586        if self.config.pretty {
27587            self.write_newline();
27588            self.indent_level += 1;
27589            for (i, expr) in regular_expressions.iter().enumerate() {
27590                if i > 0 {
27591                    self.write(",");
27592                    self.write_newline();
27593                }
27594                self.write_indent();
27595                self.generate_expression(expr)?;
27596            }
27597            self.indent_level -= 1;
27598        } else {
27599            self.write_space();
27600            for (i, expr) in regular_expressions.iter().enumerate() {
27601                if i > 0 {
27602                    self.write(", ");
27603                }
27604                self.generate_expression(expr)?;
27605            }
27606        }
27607
27608        // Output trailing WITH CUBE or WITH ROLLUP
27609        if trailing_cube {
27610            self.write_space();
27611            self.write_keyword("WITH CUBE");
27612        } else if trailing_rollup {
27613            self.write_space();
27614            self.write_keyword("WITH ROLLUP");
27615        }
27616
27617        // ClickHouse: WITH TOTALS
27618        if e.totals {
27619            self.write_space();
27620            self.write_keyword("WITH TOTALS");
27621        }
27622
27623        Ok(())
27624    }
27625
27626    fn generate_grouping(&mut self, e: &Grouping) -> Result<()> {
27627        // GROUPING(col1, col2, ...)
27628        self.write_keyword("GROUPING");
27629        self.write("(");
27630        for (i, expr) in e.expressions.iter().enumerate() {
27631            if i > 0 {
27632                self.write(", ");
27633            }
27634            self.generate_expression(expr)?;
27635        }
27636        self.write(")");
27637        Ok(())
27638    }
27639
27640    fn generate_grouping_id(&mut self, e: &GroupingId) -> Result<()> {
27641        // GROUPING_ID(col1, col2, ...)
27642        self.write_keyword("GROUPING_ID");
27643        self.write("(");
27644        for (i, expr) in e.expressions.iter().enumerate() {
27645            if i > 0 {
27646                self.write(", ");
27647            }
27648            self.generate_expression(expr)?;
27649        }
27650        self.write(")");
27651        Ok(())
27652    }
27653
27654    fn generate_grouping_sets(&mut self, e: &GroupingSets) -> Result<()> {
27655        // Python: return f"GROUPING SETS {self.wrap(grouping_sets)}"
27656        self.write_keyword("GROUPING SETS");
27657        self.write(" (");
27658        for (i, expr) in e.expressions.iter().enumerate() {
27659            if i > 0 {
27660                self.write(", ");
27661            }
27662            self.generate_expression(expr)?;
27663        }
27664        self.write(")");
27665        Ok(())
27666    }
27667
27668    fn generate_hash_agg(&mut self, e: &HashAgg) -> Result<()> {
27669        // HASH_AGG(this, expressions...)
27670        self.write_keyword("HASH_AGG");
27671        self.write("(");
27672        self.generate_expression(&e.this)?;
27673        for expr in &e.expressions {
27674            self.write(", ");
27675            self.generate_expression(expr)?;
27676        }
27677        self.write(")");
27678        Ok(())
27679    }
27680
27681    fn generate_having(&mut self, e: &Having) -> Result<()> {
27682        // Python: return f"{self.seg('HAVING')}{self.sep()}{this}"
27683        self.write_keyword("HAVING");
27684        self.write_space();
27685        self.generate_expression(&e.this)?;
27686        Ok(())
27687    }
27688
27689    fn generate_having_max(&mut self, e: &HavingMax) -> Result<()> {
27690        // Python: this HAVING MAX|MIN expression
27691        self.generate_expression(&e.this)?;
27692        self.write_space();
27693        self.write_keyword("HAVING");
27694        self.write_space();
27695        if e.max.is_some() {
27696            self.write_keyword("MAX");
27697        } else {
27698            self.write_keyword("MIN");
27699        }
27700        self.write_space();
27701        self.generate_expression(&e.expression)?;
27702        Ok(())
27703    }
27704
27705    fn generate_heredoc(&mut self, e: &Heredoc) -> Result<()> {
27706        use crate::dialects::DialectType;
27707        // DuckDB: convert dollar-tagged strings to single-quoted
27708        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
27709            // Extract the string content and output as single-quoted
27710            if let Expression::Literal(Literal::String(ref s)) = *e.this {
27711                return self.generate_string_literal(s);
27712            }
27713        }
27714        // PostgreSQL: preserve dollar-quoting
27715        if matches!(
27716            self.config.dialect,
27717            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
27718        ) {
27719            self.write("$");
27720            if let Some(tag) = &e.tag {
27721                self.generate_expression(tag)?;
27722            }
27723            self.write("$");
27724            self.generate_expression(&e.this)?;
27725            self.write("$");
27726            if let Some(tag) = &e.tag {
27727                self.generate_expression(tag)?;
27728            }
27729            self.write("$");
27730            return Ok(());
27731        }
27732        // Default: output as dollar-tagged
27733        self.write("$");
27734        if let Some(tag) = &e.tag {
27735            self.generate_expression(tag)?;
27736        }
27737        self.write("$");
27738        self.generate_expression(&e.this)?;
27739        self.write("$");
27740        if let Some(tag) = &e.tag {
27741            self.generate_expression(tag)?;
27742        }
27743        self.write("$");
27744        Ok(())
27745    }
27746
27747    fn generate_hex_encode(&mut self, e: &HexEncode) -> Result<()> {
27748        // HEX_ENCODE(this)
27749        self.write_keyword("HEX_ENCODE");
27750        self.write("(");
27751        self.generate_expression(&e.this)?;
27752        self.write(")");
27753        Ok(())
27754    }
27755
27756    fn generate_historical_data(&mut self, e: &HistoricalData) -> Result<()> {
27757        // Python: this (kind => expression)
27758        // Write the keyword (AT/BEFORE/END) directly to avoid quoting it as a reserved word
27759        match e.this.as_ref() {
27760            Expression::Identifier(id) => self.write(&id.name),
27761            other => self.generate_expression(other)?,
27762        }
27763        self.write(" (");
27764        self.write(&e.kind);
27765        self.write(" => ");
27766        self.generate_expression(&e.expression)?;
27767        self.write(")");
27768        Ok(())
27769    }
27770
27771    fn generate_hll(&mut self, e: &Hll) -> Result<()> {
27772        // HLL(this, expressions...)
27773        self.write_keyword("HLL");
27774        self.write("(");
27775        self.generate_expression(&e.this)?;
27776        for expr in &e.expressions {
27777            self.write(", ");
27778            self.generate_expression(expr)?;
27779        }
27780        self.write(")");
27781        Ok(())
27782    }
27783
27784    fn generate_in_out_column_constraint(&mut self, e: &InOutColumnConstraint) -> Result<()> {
27785        // Python: IN|OUT|IN OUT
27786        if e.input_.is_some() && e.output.is_some() {
27787            self.write_keyword("IN OUT");
27788        } else if e.input_.is_some() {
27789            self.write_keyword("IN");
27790        } else if e.output.is_some() {
27791            self.write_keyword("OUT");
27792        }
27793        Ok(())
27794    }
27795
27796    fn generate_include_property(&mut self, e: &IncludeProperty) -> Result<()> {
27797        // Python: INCLUDE this [column_def] [AS alias]
27798        self.write_keyword("INCLUDE");
27799        self.write_space();
27800        self.generate_expression(&e.this)?;
27801        if let Some(column_def) = &e.column_def {
27802            self.write_space();
27803            self.generate_expression(column_def)?;
27804        }
27805        if let Some(alias) = &e.alias {
27806            self.write_space();
27807            self.write_keyword("AS");
27808            self.write_space();
27809            self.write(alias);
27810        }
27811        Ok(())
27812    }
27813
27814    fn generate_index(&mut self, e: &Index) -> Result<()> {
27815        // [UNIQUE] [PRIMARY] [AMP] INDEX [name] [ON table] (params)
27816        if e.unique {
27817            self.write_keyword("UNIQUE");
27818            self.write_space();
27819        }
27820        if e.primary.is_some() {
27821            self.write_keyword("PRIMARY");
27822            self.write_space();
27823        }
27824        if e.amp.is_some() {
27825            self.write_keyword("AMP");
27826            self.write_space();
27827        }
27828        if e.table.is_none() {
27829            self.write_keyword("INDEX");
27830            self.write_space();
27831        }
27832        if let Some(name) = &e.this {
27833            self.generate_expression(name)?;
27834            self.write_space();
27835        }
27836        if let Some(table) = &e.table {
27837            self.write_keyword("ON");
27838            self.write_space();
27839            self.generate_expression(table)?;
27840        }
27841        if !e.params.is_empty() {
27842            self.write("(");
27843            for (i, param) in e.params.iter().enumerate() {
27844                if i > 0 {
27845                    self.write(", ");
27846                }
27847                self.generate_expression(param)?;
27848            }
27849            self.write(")");
27850        }
27851        Ok(())
27852    }
27853
27854    fn generate_index_column_constraint(&mut self, e: &IndexColumnConstraint) -> Result<()> {
27855        // Python: kind INDEX [this] [USING index_type] (expressions) [options]
27856        if let Some(kind) = &e.kind {
27857            self.write(kind);
27858            self.write_space();
27859        }
27860        self.write_keyword("INDEX");
27861        if let Some(this) = &e.this {
27862            self.write_space();
27863            self.generate_expression(this)?;
27864        }
27865        if let Some(index_type) = &e.index_type {
27866            self.write_space();
27867            self.write_keyword("USING");
27868            self.write_space();
27869            self.generate_expression(index_type)?;
27870        }
27871        if !e.expressions.is_empty() {
27872            self.write(" (");
27873            for (i, expr) in e.expressions.iter().enumerate() {
27874                if i > 0 {
27875                    self.write(", ");
27876                }
27877                self.generate_expression(expr)?;
27878            }
27879            self.write(")");
27880        }
27881        for opt in &e.options {
27882            self.write_space();
27883            self.generate_expression(opt)?;
27884        }
27885        Ok(())
27886    }
27887
27888    fn generate_index_constraint_option(&mut self, e: &IndexConstraintOption) -> Result<()> {
27889        // Python: KEY_BLOCK_SIZE = x | USING x | WITH PARSER x | COMMENT x | visible | engine_attr | secondary_engine_attr
27890        if let Some(key_block_size) = &e.key_block_size {
27891            self.write_keyword("KEY_BLOCK_SIZE");
27892            self.write(" = ");
27893            self.generate_expression(key_block_size)?;
27894        } else if let Some(using) = &e.using {
27895            self.write_keyword("USING");
27896            self.write_space();
27897            self.generate_expression(using)?;
27898        } else if let Some(parser) = &e.parser {
27899            self.write_keyword("WITH PARSER");
27900            self.write_space();
27901            self.generate_expression(parser)?;
27902        } else if let Some(comment) = &e.comment {
27903            self.write_keyword("COMMENT");
27904            self.write_space();
27905            self.generate_expression(comment)?;
27906        } else if let Some(visible) = &e.visible {
27907            self.generate_expression(visible)?;
27908        } else if let Some(engine_attr) = &e.engine_attr {
27909            self.write_keyword("ENGINE_ATTRIBUTE");
27910            self.write(" = ");
27911            self.generate_expression(engine_attr)?;
27912        } else if let Some(secondary_engine_attr) = &e.secondary_engine_attr {
27913            self.write_keyword("SECONDARY_ENGINE_ATTRIBUTE");
27914            self.write(" = ");
27915            self.generate_expression(secondary_engine_attr)?;
27916        }
27917        Ok(())
27918    }
27919
27920    fn generate_index_parameters(&mut self, e: &IndexParameters) -> Result<()> {
27921        // Python: [USING using] (columns) [PARTITION BY partition_by] [where] [INCLUDE (include)] [WITH (with_storage)] [USING INDEX TABLESPACE tablespace]
27922        if let Some(using) = &e.using {
27923            self.write_keyword("USING");
27924            self.write_space();
27925            self.generate_expression(using)?;
27926        }
27927        if !e.columns.is_empty() {
27928            self.write("(");
27929            for (i, col) in e.columns.iter().enumerate() {
27930                if i > 0 {
27931                    self.write(", ");
27932                }
27933                self.generate_expression(col)?;
27934            }
27935            self.write(")");
27936        }
27937        if let Some(partition_by) = &e.partition_by {
27938            self.write_space();
27939            self.write_keyword("PARTITION BY");
27940            self.write_space();
27941            self.generate_expression(partition_by)?;
27942        }
27943        if let Some(where_) = &e.where_ {
27944            self.write_space();
27945            self.generate_expression(where_)?;
27946        }
27947        if let Some(include) = &e.include {
27948            self.write_space();
27949            self.write_keyword("INCLUDE");
27950            self.write(" (");
27951            self.generate_expression(include)?;
27952            self.write(")");
27953        }
27954        if let Some(with_storage) = &e.with_storage {
27955            self.write_space();
27956            self.write_keyword("WITH");
27957            self.write(" (");
27958            self.generate_expression(with_storage)?;
27959            self.write(")");
27960        }
27961        if let Some(tablespace) = &e.tablespace {
27962            self.write_space();
27963            self.write_keyword("USING INDEX TABLESPACE");
27964            self.write_space();
27965            self.generate_expression(tablespace)?;
27966        }
27967        Ok(())
27968    }
27969
27970    fn generate_index_table_hint(&mut self, e: &IndexTableHint) -> Result<()> {
27971        // Python: this INDEX [FOR target] (expressions)
27972        // Write hint type (USE/IGNORE/FORCE) as keyword, not through generate_expression
27973        // to avoid quoting reserved keywords like IGNORE, FORCE, JOIN
27974        if let Expression::Identifier(id) = &*e.this {
27975            self.write_keyword(&id.name);
27976        } else {
27977            self.generate_expression(&e.this)?;
27978        }
27979        self.write_space();
27980        self.write_keyword("INDEX");
27981        if let Some(target) = &e.target {
27982            self.write_space();
27983            self.write_keyword("FOR");
27984            self.write_space();
27985            if let Expression::Identifier(id) = &**target {
27986                self.write_keyword(&id.name);
27987            } else {
27988                self.generate_expression(target)?;
27989            }
27990        }
27991        // Always output parentheses (even if empty, e.g. USE INDEX ())
27992        self.write(" (");
27993        for (i, expr) in e.expressions.iter().enumerate() {
27994            if i > 0 {
27995                self.write(", ");
27996            }
27997            self.generate_expression(expr)?;
27998        }
27999        self.write(")");
28000        Ok(())
28001    }
28002
28003    fn generate_inherits_property(&mut self, e: &InheritsProperty) -> Result<()> {
28004        // INHERITS (table1, table2, ...)
28005        self.write_keyword("INHERITS");
28006        self.write(" (");
28007        for (i, expr) in e.expressions.iter().enumerate() {
28008            if i > 0 {
28009                self.write(", ");
28010            }
28011            self.generate_expression(expr)?;
28012        }
28013        self.write(")");
28014        Ok(())
28015    }
28016
28017    fn generate_input_model_property(&mut self, e: &InputModelProperty) -> Result<()> {
28018        // INPUT(model)
28019        self.write_keyword("INPUT");
28020        self.write("(");
28021        self.generate_expression(&e.this)?;
28022        self.write(")");
28023        Ok(())
28024    }
28025
28026    fn generate_input_output_format(&mut self, e: &InputOutputFormat) -> Result<()> {
28027        // Python: INPUTFORMAT input_format OUTPUTFORMAT output_format
28028        if let Some(input_format) = &e.input_format {
28029            self.write_keyword("INPUTFORMAT");
28030            self.write_space();
28031            self.generate_expression(input_format)?;
28032        }
28033        if let Some(output_format) = &e.output_format {
28034            if e.input_format.is_some() {
28035                self.write(" ");
28036            }
28037            self.write_keyword("OUTPUTFORMAT");
28038            self.write_space();
28039            self.generate_expression(output_format)?;
28040        }
28041        Ok(())
28042    }
28043
28044    fn generate_install(&mut self, e: &Install) -> Result<()> {
28045        // [FORCE] INSTALL extension [FROM source]
28046        if e.force.is_some() {
28047            self.write_keyword("FORCE");
28048            self.write_space();
28049        }
28050        self.write_keyword("INSTALL");
28051        self.write_space();
28052        self.generate_expression(&e.this)?;
28053        if let Some(from) = &e.from_ {
28054            self.write_space();
28055            self.write_keyword("FROM");
28056            self.write_space();
28057            self.generate_expression(from)?;
28058        }
28059        Ok(())
28060    }
28061
28062    fn generate_interval_op(&mut self, e: &IntervalOp) -> Result<()> {
28063        // INTERVAL 'expression' unit
28064        self.write_keyword("INTERVAL");
28065        self.write_space();
28066        // When a unit is specified and the expression is a number,
28067        self.generate_expression(&e.expression)?;
28068        if let Some(unit) = &e.unit {
28069            self.write_space();
28070            self.write(unit);
28071        }
28072        Ok(())
28073    }
28074
28075    fn generate_interval_span(&mut self, e: &IntervalSpan) -> Result<()> {
28076        // unit TO unit (e.g., HOUR TO SECOND)
28077        self.write(&format!("{:?}", e.this).to_uppercase());
28078        self.write_space();
28079        self.write_keyword("TO");
28080        self.write_space();
28081        self.write(&format!("{:?}", e.expression).to_uppercase());
28082        Ok(())
28083    }
28084
28085    fn generate_into_clause(&mut self, e: &IntoClause) -> Result<()> {
28086        // INTO [TEMPORARY|UNLOGGED] table
28087        self.write_keyword("INTO");
28088        if e.temporary {
28089            self.write_keyword(" TEMPORARY");
28090        }
28091        if e.unlogged.is_some() {
28092            self.write_keyword(" UNLOGGED");
28093        }
28094        if let Some(this) = &e.this {
28095            self.write_space();
28096            self.generate_expression(this)?;
28097        }
28098        if !e.expressions.is_empty() {
28099            self.write(" (");
28100            for (i, expr) in e.expressions.iter().enumerate() {
28101                if i > 0 {
28102                    self.write(", ");
28103                }
28104                self.generate_expression(expr)?;
28105            }
28106            self.write(")");
28107        }
28108        Ok(())
28109    }
28110
28111    fn generate_introducer(&mut self, e: &Introducer) -> Result<()> {
28112        // Python: this expression (e.g., _utf8 'string')
28113        self.generate_expression(&e.this)?;
28114        self.write_space();
28115        self.generate_expression(&e.expression)?;
28116        Ok(())
28117    }
28118
28119    fn generate_isolated_loading_property(&mut self, e: &IsolatedLoadingProperty) -> Result<()> {
28120        // Python: WITH [NO] [CONCURRENT] ISOLATED LOADING [target]
28121        self.write_keyword("WITH");
28122        if e.no.is_some() {
28123            self.write_keyword(" NO");
28124        }
28125        if e.concurrent.is_some() {
28126            self.write_keyword(" CONCURRENT");
28127        }
28128        self.write_keyword(" ISOLATED LOADING");
28129        if let Some(target) = &e.target {
28130            self.write_space();
28131            self.generate_expression(target)?;
28132        }
28133        Ok(())
28134    }
28135
28136    fn generate_json(&mut self, e: &JSON) -> Result<()> {
28137        // Python: JSON [this] [WITHOUT|WITH] [UNIQUE KEYS]
28138        self.write_keyword("JSON");
28139        if let Some(this) = &e.this {
28140            self.write_space();
28141            self.generate_expression(this)?;
28142        }
28143        if let Some(with_) = &e.with_ {
28144            // Check if it's a truthy boolean
28145            if let Expression::Boolean(b) = with_.as_ref() {
28146                if b.value {
28147                    self.write_keyword(" WITH");
28148                } else {
28149                    self.write_keyword(" WITHOUT");
28150                }
28151            }
28152        }
28153        if e.unique {
28154            self.write_keyword(" UNIQUE KEYS");
28155        }
28156        Ok(())
28157    }
28158
28159    fn generate_json_array(&mut self, e: &JSONArray) -> Result<()> {
28160        // Python: return self.func("JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})")
28161        self.write_keyword("JSON_ARRAY");
28162        self.write("(");
28163        for (i, expr) in e.expressions.iter().enumerate() {
28164            if i > 0 {
28165                self.write(", ");
28166            }
28167            self.generate_expression(expr)?;
28168        }
28169        if let Some(null_handling) = &e.null_handling {
28170            self.write_space();
28171            self.generate_expression(null_handling)?;
28172        }
28173        if let Some(return_type) = &e.return_type {
28174            self.write_space();
28175            self.write_keyword("RETURNING");
28176            self.write_space();
28177            self.generate_expression(return_type)?;
28178        }
28179        if e.strict.is_some() {
28180            self.write_space();
28181            self.write_keyword("STRICT");
28182        }
28183        self.write(")");
28184        Ok(())
28185    }
28186
28187    fn generate_json_array_agg_struct(&mut self, e: &JSONArrayAgg) -> Result<()> {
28188        // JSON_ARRAYAGG(this [ORDER BY ...] [NULL ON NULL | ABSENT ON NULL] [RETURNING type] [STRICT])
28189        self.write_keyword("JSON_ARRAYAGG");
28190        self.write("(");
28191        self.generate_expression(&e.this)?;
28192        if let Some(order) = &e.order {
28193            self.write_space();
28194            // Order is stored as an OrderBy expression
28195            if let Expression::OrderBy(ob) = order.as_ref() {
28196                self.write_keyword("ORDER BY");
28197                self.write_space();
28198                for (i, ord) in ob.expressions.iter().enumerate() {
28199                    if i > 0 {
28200                        self.write(", ");
28201                    }
28202                    self.generate_ordered(ord)?;
28203                }
28204            } else {
28205                // Fallback: generate the expression directly
28206                self.generate_expression(order)?;
28207            }
28208        }
28209        if let Some(null_handling) = &e.null_handling {
28210            self.write_space();
28211            self.generate_expression(null_handling)?;
28212        }
28213        if let Some(return_type) = &e.return_type {
28214            self.write_space();
28215            self.write_keyword("RETURNING");
28216            self.write_space();
28217            self.generate_expression(return_type)?;
28218        }
28219        if e.strict.is_some() {
28220            self.write_space();
28221            self.write_keyword("STRICT");
28222        }
28223        self.write(")");
28224        Ok(())
28225    }
28226
28227    fn generate_json_object_agg_struct(&mut self, e: &JSONObjectAgg) -> Result<()> {
28228        // JSON_OBJECTAGG(key: value [NULL ON NULL | ABSENT ON NULL] [WITH UNIQUE KEYS] [RETURNING type])
28229        self.write_keyword("JSON_OBJECTAGG");
28230        self.write("(");
28231        for (i, expr) in e.expressions.iter().enumerate() {
28232            if i > 0 {
28233                self.write(", ");
28234            }
28235            self.generate_expression(expr)?;
28236        }
28237        if let Some(null_handling) = &e.null_handling {
28238            self.write_space();
28239            self.generate_expression(null_handling)?;
28240        }
28241        if let Some(unique_keys) = &e.unique_keys {
28242            self.write_space();
28243            if let Expression::Boolean(b) = unique_keys.as_ref() {
28244                if b.value {
28245                    self.write_keyword("WITH UNIQUE KEYS");
28246                } else {
28247                    self.write_keyword("WITHOUT UNIQUE KEYS");
28248                }
28249            }
28250        }
28251        if let Some(return_type) = &e.return_type {
28252            self.write_space();
28253            self.write_keyword("RETURNING");
28254            self.write_space();
28255            self.generate_expression(return_type)?;
28256        }
28257        self.write(")");
28258        Ok(())
28259    }
28260
28261    fn generate_json_array_append(&mut self, e: &JSONArrayAppend) -> Result<()> {
28262        // JSON_ARRAY_APPEND(this, path, value, ...)
28263        self.write_keyword("JSON_ARRAY_APPEND");
28264        self.write("(");
28265        self.generate_expression(&e.this)?;
28266        for expr in &e.expressions {
28267            self.write(", ");
28268            self.generate_expression(expr)?;
28269        }
28270        self.write(")");
28271        Ok(())
28272    }
28273
28274    fn generate_json_array_contains(&mut self, e: &JSONArrayContains) -> Result<()> {
28275        // JSON_ARRAY_CONTAINS(this, expression)
28276        self.write_keyword("JSON_ARRAY_CONTAINS");
28277        self.write("(");
28278        self.generate_expression(&e.this)?;
28279        self.write(", ");
28280        self.generate_expression(&e.expression)?;
28281        self.write(")");
28282        Ok(())
28283    }
28284
28285    fn generate_json_array_insert(&mut self, e: &JSONArrayInsert) -> Result<()> {
28286        // JSON_ARRAY_INSERT(this, path, value, ...)
28287        self.write_keyword("JSON_ARRAY_INSERT");
28288        self.write("(");
28289        self.generate_expression(&e.this)?;
28290        for expr in &e.expressions {
28291            self.write(", ");
28292            self.generate_expression(expr)?;
28293        }
28294        self.write(")");
28295        Ok(())
28296    }
28297
28298    fn generate_jsonb_exists(&mut self, e: &JSONBExists) -> Result<()> {
28299        // JSONB_EXISTS(this, path)
28300        self.write_keyword("JSONB_EXISTS");
28301        self.write("(");
28302        self.generate_expression(&e.this)?;
28303        if let Some(path) = &e.path {
28304            self.write(", ");
28305            self.generate_expression(path)?;
28306        }
28307        self.write(")");
28308        Ok(())
28309    }
28310
28311    fn generate_jsonb_extract_scalar(&mut self, e: &JSONBExtractScalar) -> Result<()> {
28312        // JSONB_EXTRACT_SCALAR(this, expression)
28313        self.write_keyword("JSONB_EXTRACT_SCALAR");
28314        self.write("(");
28315        self.generate_expression(&e.this)?;
28316        self.write(", ");
28317        self.generate_expression(&e.expression)?;
28318        self.write(")");
28319        Ok(())
28320    }
28321
28322    fn generate_jsonb_object_agg(&mut self, e: &JSONBObjectAgg) -> Result<()> {
28323        // JSONB_OBJECT_AGG(this, expression)
28324        self.write_keyword("JSONB_OBJECT_AGG");
28325        self.write("(");
28326        self.generate_expression(&e.this)?;
28327        self.write(", ");
28328        self.generate_expression(&e.expression)?;
28329        self.write(")");
28330        Ok(())
28331    }
28332
28333    fn generate_json_column_def(&mut self, e: &JSONColumnDef) -> Result<()> {
28334        // Python: NESTED PATH path schema | this kind PATH path [FOR ORDINALITY]
28335        if let Some(nested_schema) = &e.nested_schema {
28336            self.write_keyword("NESTED");
28337            if let Some(path) = &e.path {
28338                self.write_space();
28339                self.write_keyword("PATH");
28340                self.write_space();
28341                self.generate_expression(path)?;
28342            }
28343            self.write_space();
28344            self.generate_expression(nested_schema)?;
28345        } else {
28346            if let Some(this) = &e.this {
28347                self.generate_expression(this)?;
28348            }
28349            if let Some(kind) = &e.kind {
28350                self.write_space();
28351                self.write(kind);
28352            }
28353            if let Some(path) = &e.path {
28354                self.write_space();
28355                self.write_keyword("PATH");
28356                self.write_space();
28357                self.generate_expression(path)?;
28358            }
28359            if e.ordinality.is_some() {
28360                self.write_keyword(" FOR ORDINALITY");
28361            }
28362        }
28363        Ok(())
28364    }
28365
28366    fn generate_json_exists(&mut self, e: &JSONExists) -> Result<()> {
28367        // JSON_EXISTS(this, path PASSING vars ON ERROR/EMPTY condition)
28368        self.write_keyword("JSON_EXISTS");
28369        self.write("(");
28370        self.generate_expression(&e.this)?;
28371        if let Some(path) = &e.path {
28372            self.write(", ");
28373            self.generate_expression(path)?;
28374        }
28375        if let Some(passing) = &e.passing {
28376            self.write_space();
28377            self.write_keyword("PASSING");
28378            self.write_space();
28379            self.generate_expression(passing)?;
28380        }
28381        if let Some(on_condition) = &e.on_condition {
28382            self.write_space();
28383            self.generate_expression(on_condition)?;
28384        }
28385        self.write(")");
28386        Ok(())
28387    }
28388
28389    fn generate_json_cast(&mut self, e: &JSONCast) -> Result<()> {
28390        self.generate_expression(&e.this)?;
28391        self.write(".:");
28392        self.generate_data_type(&e.to)?;
28393        Ok(())
28394    }
28395
28396    fn generate_json_extract_array(&mut self, e: &JSONExtractArray) -> Result<()> {
28397        // JSON_EXTRACT_ARRAY(this, expression)
28398        self.write_keyword("JSON_EXTRACT_ARRAY");
28399        self.write("(");
28400        self.generate_expression(&e.this)?;
28401        if let Some(expr) = &e.expression {
28402            self.write(", ");
28403            self.generate_expression(expr)?;
28404        }
28405        self.write(")");
28406        Ok(())
28407    }
28408
28409    fn generate_json_extract_quote(&mut self, e: &JSONExtractQuote) -> Result<()> {
28410        // Snowflake: KEEP [OMIT] QUOTES [SCALAR_ONLY] for JSON extraction
28411        if let Some(option) = &e.option {
28412            self.generate_expression(option)?;
28413            self.write_space();
28414        }
28415        self.write_keyword("QUOTES");
28416        if e.scalar.is_some() {
28417            self.write_keyword(" SCALAR_ONLY");
28418        }
28419        Ok(())
28420    }
28421
28422    fn generate_json_extract_scalar(&mut self, e: &JSONExtractScalar) -> Result<()> {
28423        // JSON_EXTRACT_SCALAR(this, expression)
28424        self.write_keyword("JSON_EXTRACT_SCALAR");
28425        self.write("(");
28426        self.generate_expression(&e.this)?;
28427        self.write(", ");
28428        self.generate_expression(&e.expression)?;
28429        self.write(")");
28430        Ok(())
28431    }
28432
28433    fn generate_json_extract_path(&mut self, e: &JSONExtract) -> Result<()> {
28434        // For variant_extract (Snowflake/Databricks colon syntax like a:field)
28435        // Databricks uses col:path syntax, Snowflake uses GET_PATH(col, 'path')
28436        // Otherwise output JSON_EXTRACT(this, expression)
28437        if e.variant_extract.is_some() {
28438            use crate::dialects::DialectType;
28439            if matches!(self.config.dialect, Some(DialectType::Databricks)) {
28440                // Databricks: output col:path syntax (e.g., c1:price, c1:price.foo, c1:price.bar[1])
28441                self.generate_expression(&e.this)?;
28442                self.write(":");
28443                // The expression is a string literal containing the path (e.g., 'price' or 'price.foo')
28444                // We need to output it without quotes
28445                match e.expression.as_ref() {
28446                    Expression::Literal(Literal::String(s)) => {
28447                        self.write(s);
28448                    }
28449                    _ => {
28450                        // Fallback: generate as-is (shouldn't happen in typical cases)
28451                        self.generate_expression(&e.expression)?;
28452                    }
28453                }
28454            } else {
28455                // Snowflake and others: use GET_PATH(col, 'path')
28456                self.write_keyword("GET_PATH");
28457                self.write("(");
28458                self.generate_expression(&e.this)?;
28459                self.write(", ");
28460                self.generate_expression(&e.expression)?;
28461                self.write(")");
28462            }
28463        } else {
28464            self.write_keyword("JSON_EXTRACT");
28465            self.write("(");
28466            self.generate_expression(&e.this)?;
28467            self.write(", ");
28468            self.generate_expression(&e.expression)?;
28469            for expr in &e.expressions {
28470                self.write(", ");
28471                self.generate_expression(expr)?;
28472            }
28473            self.write(")");
28474        }
28475        Ok(())
28476    }
28477
28478    fn generate_json_format(&mut self, e: &JSONFormat) -> Result<()> {
28479        // Output: {expr} FORMAT JSON
28480        // This wraps an expression with FORMAT JSON suffix (Oracle JSON function syntax)
28481        if let Some(this) = &e.this {
28482            self.generate_expression(this)?;
28483            self.write_space();
28484        }
28485        self.write_keyword("FORMAT JSON");
28486        Ok(())
28487    }
28488
28489    fn generate_json_key_value(&mut self, e: &JSONKeyValue) -> Result<()> {
28490        // key: value (for JSON objects)
28491        self.generate_expression(&e.this)?;
28492        self.write(": ");
28493        self.generate_expression(&e.expression)?;
28494        Ok(())
28495    }
28496
28497    fn generate_json_keys(&mut self, e: &JSONKeys) -> Result<()> {
28498        // JSON_KEYS(this, expression, expressions...)
28499        self.write_keyword("JSON_KEYS");
28500        self.write("(");
28501        self.generate_expression(&e.this)?;
28502        if let Some(expr) = &e.expression {
28503            self.write(", ");
28504            self.generate_expression(expr)?;
28505        }
28506        for expr in &e.expressions {
28507            self.write(", ");
28508            self.generate_expression(expr)?;
28509        }
28510        self.write(")");
28511        Ok(())
28512    }
28513
28514    fn generate_json_keys_at_depth(&mut self, e: &JSONKeysAtDepth) -> Result<()> {
28515        // JSON_KEYS(this, expression)
28516        self.write_keyword("JSON_KEYS");
28517        self.write("(");
28518        self.generate_expression(&e.this)?;
28519        if let Some(expr) = &e.expression {
28520            self.write(", ");
28521            self.generate_expression(expr)?;
28522        }
28523        self.write(")");
28524        Ok(())
28525    }
28526
28527    fn generate_json_path_expr(&mut self, e: &JSONPath) -> Result<()> {
28528        // JSONPath expression: generates a quoted path like '$.foo' or '$[0]'
28529        // The path components are concatenated without spaces
28530        let mut path_str = String::new();
28531        for expr in &e.expressions {
28532            match expr {
28533                Expression::JSONPathRoot(_) => {
28534                    path_str.push('$');
28535                }
28536                Expression::JSONPathKey(k) => {
28537                    // .key or ."key" (quote if key has special characters)
28538                    if let Expression::Literal(crate::expressions::Literal::String(s)) =
28539                        k.this.as_ref()
28540                    {
28541                        path_str.push('.');
28542                        // Quote the key if it contains non-alphanumeric characters (hyphens, spaces, etc.)
28543                        let needs_quoting = s.chars().any(|c| !c.is_alphanumeric() && c != '_');
28544                        if needs_quoting {
28545                            path_str.push('"');
28546                            path_str.push_str(s);
28547                            path_str.push('"');
28548                        } else {
28549                            path_str.push_str(s);
28550                        }
28551                    }
28552                }
28553                Expression::JSONPathSubscript(s) => {
28554                    // [index]
28555                    if let Expression::Literal(crate::expressions::Literal::Number(n)) =
28556                        s.this.as_ref()
28557                    {
28558                        path_str.push('[');
28559                        path_str.push_str(n);
28560                        path_str.push(']');
28561                    }
28562                }
28563                _ => {
28564                    // For other path parts, try to generate them
28565                    let mut temp_gen = Self::with_config(self.config.clone());
28566                    temp_gen.generate_expression(expr)?;
28567                    path_str.push_str(&temp_gen.output);
28568                }
28569            }
28570        }
28571        // Output as quoted string
28572        self.write("'");
28573        self.write(&path_str);
28574        self.write("'");
28575        Ok(())
28576    }
28577
28578    fn generate_json_path_filter(&mut self, e: &JSONPathFilter) -> Result<()> {
28579        // JSON path filter: ?(predicate)
28580        self.write("?(");
28581        self.generate_expression(&e.this)?;
28582        self.write(")");
28583        Ok(())
28584    }
28585
28586    fn generate_json_path_key(&mut self, e: &JSONPathKey) -> Result<()> {
28587        // JSON path key: .key or ["key"]
28588        self.write(".");
28589        self.generate_expression(&e.this)?;
28590        Ok(())
28591    }
28592
28593    fn generate_json_path_recursive(&mut self, e: &JSONPathRecursive) -> Result<()> {
28594        // JSON path recursive descent: ..
28595        self.write("..");
28596        if let Some(this) = &e.this {
28597            self.generate_expression(this)?;
28598        }
28599        Ok(())
28600    }
28601
28602    fn generate_json_path_root(&mut self) -> Result<()> {
28603        // JSON path root: $
28604        self.write("$");
28605        Ok(())
28606    }
28607
28608    fn generate_json_path_script(&mut self, e: &JSONPathScript) -> Result<()> {
28609        // JSON path script: (expression)
28610        self.write("(");
28611        self.generate_expression(&e.this)?;
28612        self.write(")");
28613        Ok(())
28614    }
28615
28616    fn generate_json_path_selector(&mut self, e: &JSONPathSelector) -> Result<()> {
28617        // JSON path selector: *
28618        self.generate_expression(&e.this)?;
28619        Ok(())
28620    }
28621
28622    fn generate_json_path_slice(&mut self, e: &JSONPathSlice) -> Result<()> {
28623        // JSON path slice: [start:end:step]
28624        self.write("[");
28625        if let Some(start) = &e.start {
28626            self.generate_expression(start)?;
28627        }
28628        self.write(":");
28629        if let Some(end) = &e.end {
28630            self.generate_expression(end)?;
28631        }
28632        if let Some(step) = &e.step {
28633            self.write(":");
28634            self.generate_expression(step)?;
28635        }
28636        self.write("]");
28637        Ok(())
28638    }
28639
28640    fn generate_json_path_subscript(&mut self, e: &JSONPathSubscript) -> Result<()> {
28641        // JSON path subscript: [index] or [*]
28642        self.write("[");
28643        self.generate_expression(&e.this)?;
28644        self.write("]");
28645        Ok(())
28646    }
28647
28648    fn generate_json_path_union(&mut self, e: &JSONPathUnion) -> Result<()> {
28649        // JSON path union: [key1, key2, ...]
28650        self.write("[");
28651        for (i, expr) in e.expressions.iter().enumerate() {
28652            if i > 0 {
28653                self.write(", ");
28654            }
28655            self.generate_expression(expr)?;
28656        }
28657        self.write("]");
28658        Ok(())
28659    }
28660
28661    fn generate_json_remove(&mut self, e: &JSONRemove) -> Result<()> {
28662        // JSON_REMOVE(this, path1, path2, ...)
28663        self.write_keyword("JSON_REMOVE");
28664        self.write("(");
28665        self.generate_expression(&e.this)?;
28666        for expr in &e.expressions {
28667            self.write(", ");
28668            self.generate_expression(expr)?;
28669        }
28670        self.write(")");
28671        Ok(())
28672    }
28673
28674    fn generate_json_schema(&mut self, e: &JSONSchema) -> Result<()> {
28675        // COLUMNS(col1 type, col2 type, ...)
28676        // When pretty printing and content is too wide, format with each column on a separate line
28677        self.write_keyword("COLUMNS");
28678        self.write("(");
28679
28680        if self.config.pretty && !e.expressions.is_empty() {
28681            // First, generate all expressions into strings to check width
28682            let mut expr_strings: Vec<String> = Vec::with_capacity(e.expressions.len());
28683            for expr in &e.expressions {
28684                let mut temp_gen = Generator::with_config(self.config.clone());
28685                temp_gen.generate_expression(expr)?;
28686                expr_strings.push(temp_gen.output);
28687            }
28688
28689            // Check if total width exceeds max_text_width
28690            if self.too_wide(&expr_strings) {
28691                // Pretty print: each column on its own line
28692                self.write_newline();
28693                self.indent_level += 1;
28694                for (i, expr_str) in expr_strings.iter().enumerate() {
28695                    if i > 0 {
28696                        self.write(",");
28697                        self.write_newline();
28698                    }
28699                    self.write_indent();
28700                    self.write(expr_str);
28701                }
28702                self.write_newline();
28703                self.indent_level -= 1;
28704                self.write_indent();
28705            } else {
28706                // Compact: all on one line
28707                for (i, expr_str) in expr_strings.iter().enumerate() {
28708                    if i > 0 {
28709                        self.write(", ");
28710                    }
28711                    self.write(expr_str);
28712                }
28713            }
28714        } else {
28715            // Non-pretty mode: compact format
28716            for (i, expr) in e.expressions.iter().enumerate() {
28717                if i > 0 {
28718                    self.write(", ");
28719                }
28720                self.generate_expression(expr)?;
28721            }
28722        }
28723        self.write(")");
28724        Ok(())
28725    }
28726
28727    fn generate_json_set(&mut self, e: &JSONSet) -> Result<()> {
28728        // JSON_SET(this, path, value, ...)
28729        self.write_keyword("JSON_SET");
28730        self.write("(");
28731        self.generate_expression(&e.this)?;
28732        for expr in &e.expressions {
28733            self.write(", ");
28734            self.generate_expression(expr)?;
28735        }
28736        self.write(")");
28737        Ok(())
28738    }
28739
28740    fn generate_json_strip_nulls(&mut self, e: &JSONStripNulls) -> Result<()> {
28741        // JSON_STRIP_NULLS(this, expression)
28742        self.write_keyword("JSON_STRIP_NULLS");
28743        self.write("(");
28744        self.generate_expression(&e.this)?;
28745        if let Some(expr) = &e.expression {
28746            self.write(", ");
28747            self.generate_expression(expr)?;
28748        }
28749        self.write(")");
28750        Ok(())
28751    }
28752
28753    fn generate_json_table(&mut self, e: &JSONTable) -> Result<()> {
28754        // JSON_TABLE(this, path [error_handling] [empty_handling] schema)
28755        self.write_keyword("JSON_TABLE");
28756        self.write("(");
28757        self.generate_expression(&e.this)?;
28758        if let Some(path) = &e.path {
28759            self.write(", ");
28760            self.generate_expression(path)?;
28761        }
28762        if let Some(error_handling) = &e.error_handling {
28763            self.write_space();
28764            self.generate_expression(error_handling)?;
28765        }
28766        if let Some(empty_handling) = &e.empty_handling {
28767            self.write_space();
28768            self.generate_expression(empty_handling)?;
28769        }
28770        if let Some(schema) = &e.schema {
28771            self.write_space();
28772            self.generate_expression(schema)?;
28773        }
28774        self.write(")");
28775        Ok(())
28776    }
28777
28778    fn generate_json_type(&mut self, e: &JSONType) -> Result<()> {
28779        // JSON_TYPE(this)
28780        self.write_keyword("JSON_TYPE");
28781        self.write("(");
28782        self.generate_expression(&e.this)?;
28783        self.write(")");
28784        Ok(())
28785    }
28786
28787    fn generate_json_value(&mut self, e: &JSONValue) -> Result<()> {
28788        // JSON_VALUE(this, path RETURNING type ON condition)
28789        self.write_keyword("JSON_VALUE");
28790        self.write("(");
28791        self.generate_expression(&e.this)?;
28792        if let Some(path) = &e.path {
28793            self.write(", ");
28794            self.generate_expression(path)?;
28795        }
28796        if let Some(returning) = &e.returning {
28797            self.write_space();
28798            self.write_keyword("RETURNING");
28799            self.write_space();
28800            self.generate_expression(returning)?;
28801        }
28802        if let Some(on_condition) = &e.on_condition {
28803            self.write_space();
28804            self.generate_expression(on_condition)?;
28805        }
28806        self.write(")");
28807        Ok(())
28808    }
28809
28810    fn generate_json_value_array(&mut self, e: &JSONValueArray) -> Result<()> {
28811        // JSON_VALUE_ARRAY(this)
28812        self.write_keyword("JSON_VALUE_ARRAY");
28813        self.write("(");
28814        self.generate_expression(&e.this)?;
28815        self.write(")");
28816        Ok(())
28817    }
28818
28819    fn generate_jarowinkler_similarity(&mut self, e: &JarowinklerSimilarity) -> Result<()> {
28820        // JAROWINKLER_SIMILARITY(str1, str2)
28821        self.write_keyword("JAROWINKLER_SIMILARITY");
28822        self.write("(");
28823        self.generate_expression(&e.this)?;
28824        self.write(", ");
28825        self.generate_expression(&e.expression)?;
28826        self.write(")");
28827        Ok(())
28828    }
28829
28830    fn generate_join_hint(&mut self, e: &JoinHint) -> Result<()> {
28831        // Python: this(expressions)
28832        self.generate_expression(&e.this)?;
28833        self.write("(");
28834        for (i, expr) in e.expressions.iter().enumerate() {
28835            if i > 0 {
28836                self.write(", ");
28837            }
28838            self.generate_expression(expr)?;
28839        }
28840        self.write(")");
28841        Ok(())
28842    }
28843
28844    fn generate_journal_property(&mut self, e: &JournalProperty) -> Result<()> {
28845        // Python: {no}{local}{dual}{before}{after}JOURNAL
28846        if e.no.is_some() {
28847            self.write_keyword("NO ");
28848        }
28849        if let Some(local) = &e.local {
28850            self.generate_expression(local)?;
28851            self.write_space();
28852        }
28853        if e.dual.is_some() {
28854            self.write_keyword("DUAL ");
28855        }
28856        if e.before.is_some() {
28857            self.write_keyword("BEFORE ");
28858        }
28859        if e.after.is_some() {
28860            self.write_keyword("AFTER ");
28861        }
28862        self.write_keyword("JOURNAL");
28863        Ok(())
28864    }
28865
28866    fn generate_language_property(&mut self, e: &LanguageProperty) -> Result<()> {
28867        // LANGUAGE language_name
28868        self.write_keyword("LANGUAGE");
28869        self.write_space();
28870        self.generate_expression(&e.this)?;
28871        Ok(())
28872    }
28873
28874    fn generate_lateral(&mut self, e: &Lateral) -> Result<()> {
28875        // Python: handles LATERAL VIEW (Hive/Spark) and regular LATERAL
28876        if e.view.is_some() {
28877            // LATERAL VIEW [OUTER] expression [alias] [AS columns]
28878            self.write_keyword("LATERAL VIEW");
28879            if e.outer.is_some() {
28880                self.write_space();
28881                self.write_keyword("OUTER");
28882            }
28883            self.write_space();
28884            self.generate_expression(&e.this)?;
28885            if let Some(alias) = &e.alias {
28886                self.write_space();
28887                self.write(alias);
28888            }
28889        } else {
28890            // LATERAL subquery/function [WITH ORDINALITY] [AS alias(columns)]
28891            self.write_keyword("LATERAL");
28892            self.write_space();
28893            self.generate_expression(&e.this)?;
28894            if e.ordinality.is_some() {
28895                self.write_space();
28896                self.write_keyword("WITH ORDINALITY");
28897            }
28898            if let Some(alias) = &e.alias {
28899                self.write_space();
28900                self.write_keyword("AS");
28901                self.write_space();
28902                self.write(alias);
28903                if !e.column_aliases.is_empty() {
28904                    self.write("(");
28905                    for (i, col) in e.column_aliases.iter().enumerate() {
28906                        if i > 0 {
28907                            self.write(", ");
28908                        }
28909                        self.write(col);
28910                    }
28911                    self.write(")");
28912                }
28913            }
28914        }
28915        Ok(())
28916    }
28917
28918    fn generate_like_property(&mut self, e: &LikeProperty) -> Result<()> {
28919        // Python: LIKE this [options]
28920        self.write_keyword("LIKE");
28921        self.write_space();
28922        self.generate_expression(&e.this)?;
28923        for expr in &e.expressions {
28924            self.write_space();
28925            self.generate_expression(expr)?;
28926        }
28927        Ok(())
28928    }
28929
28930    fn generate_limit(&mut self, e: &Limit) -> Result<()> {
28931        self.write_keyword("LIMIT");
28932        self.write_space();
28933        self.write_limit_expr(&e.this)?;
28934        if e.percent {
28935            self.write_space();
28936            self.write_keyword("PERCENT");
28937        }
28938        // Emit any comments that were captured from before the LIMIT keyword
28939        for comment in &e.comments {
28940            self.write(" ");
28941            self.write_formatted_comment(comment);
28942        }
28943        Ok(())
28944    }
28945
28946    fn generate_limit_options(&mut self, e: &LimitOptions) -> Result<()> {
28947        // Python: [PERCENT][ROWS][WITH TIES|ONLY]
28948        if e.percent.is_some() {
28949            self.write_keyword(" PERCENT");
28950        }
28951        if e.rows.is_some() {
28952            self.write_keyword(" ROWS");
28953        }
28954        if e.with_ties.is_some() {
28955            self.write_keyword(" WITH TIES");
28956        } else if e.rows.is_some() {
28957            self.write_keyword(" ONLY");
28958        }
28959        Ok(())
28960    }
28961
28962    fn generate_list(&mut self, e: &List) -> Result<()> {
28963        use crate::dialects::DialectType;
28964        let is_materialize = matches!(self.config.dialect, Some(DialectType::Materialize));
28965
28966        // Check if this is a subquery-based list (LIST(SELECT ...))
28967        if e.expressions.len() == 1 {
28968            if let Expression::Select(_) = &e.expressions[0] {
28969                self.write_keyword("LIST");
28970                self.write("(");
28971                self.generate_expression(&e.expressions[0])?;
28972                self.write(")");
28973                return Ok(());
28974            }
28975        }
28976
28977        // For Materialize, output as LIST[expr, expr, ...]
28978        if is_materialize {
28979            self.write_keyword("LIST");
28980            self.write("[");
28981            for (i, expr) in e.expressions.iter().enumerate() {
28982                if i > 0 {
28983                    self.write(", ");
28984                }
28985                self.generate_expression(expr)?;
28986            }
28987            self.write("]");
28988        } else {
28989            // For other dialects, output as LIST(expr, expr, ...)
28990            self.write_keyword("LIST");
28991            self.write("(");
28992            for (i, expr) in e.expressions.iter().enumerate() {
28993                if i > 0 {
28994                    self.write(", ");
28995                }
28996                self.generate_expression(expr)?;
28997            }
28998            self.write(")");
28999        }
29000        Ok(())
29001    }
29002
29003    fn generate_tomap(&mut self, e: &ToMap) -> Result<()> {
29004        // Check if this is a subquery-based map (MAP(SELECT ...))
29005        if let Expression::Select(_) = &*e.this {
29006            self.write_keyword("MAP");
29007            self.write("(");
29008            self.generate_expression(&e.this)?;
29009            self.write(")");
29010            return Ok(());
29011        }
29012
29013        let is_duckdb = matches!(self.config.dialect, Some(DialectType::DuckDB));
29014
29015        // For Struct-based map: DuckDB uses MAP {'key': value}, Materialize uses MAP['key' => value]
29016        self.write_keyword("MAP");
29017        if is_duckdb {
29018            self.write(" {");
29019        } else {
29020            self.write("[");
29021        }
29022        if let Expression::Struct(s) = &*e.this {
29023            for (i, (_, expr)) in s.fields.iter().enumerate() {
29024                if i > 0 {
29025                    self.write(", ");
29026                }
29027                if let Expression::PropertyEQ(op) = expr {
29028                    self.generate_expression(&op.left)?;
29029                    if is_duckdb {
29030                        self.write(": ");
29031                    } else {
29032                        self.write(" => ");
29033                    }
29034                    self.generate_expression(&op.right)?;
29035                } else {
29036                    self.generate_expression(expr)?;
29037                }
29038            }
29039        }
29040        if is_duckdb {
29041            self.write("}");
29042        } else {
29043            self.write("]");
29044        }
29045        Ok(())
29046    }
29047
29048    fn generate_localtime(&mut self, e: &Localtime) -> Result<()> {
29049        // Python: LOCALTIME or LOCALTIME(precision)
29050        self.write_keyword("LOCALTIME");
29051        if let Some(precision) = &e.this {
29052            self.write("(");
29053            self.generate_expression(precision)?;
29054            self.write(")");
29055        }
29056        Ok(())
29057    }
29058
29059    fn generate_localtimestamp(&mut self, e: &Localtimestamp) -> Result<()> {
29060        // Python: LOCALTIMESTAMP or LOCALTIMESTAMP(precision)
29061        self.write_keyword("LOCALTIMESTAMP");
29062        if let Some(precision) = &e.this {
29063            self.write("(");
29064            self.generate_expression(precision)?;
29065            self.write(")");
29066        }
29067        Ok(())
29068    }
29069
29070    fn generate_location_property(&mut self, e: &LocationProperty) -> Result<()> {
29071        // LOCATION 'path'
29072        self.write_keyword("LOCATION");
29073        self.write_space();
29074        self.generate_expression(&e.this)?;
29075        Ok(())
29076    }
29077
29078    fn generate_lock(&mut self, e: &Lock) -> Result<()> {
29079        // Python: FOR UPDATE|FOR SHARE [OF tables] [NOWAIT|WAIT n]
29080        if e.update.is_some() {
29081            if e.key.is_some() {
29082                self.write_keyword("FOR NO KEY UPDATE");
29083            } else {
29084                self.write_keyword("FOR UPDATE");
29085            }
29086        } else {
29087            if e.key.is_some() {
29088                self.write_keyword("FOR KEY SHARE");
29089            } else {
29090                self.write_keyword("FOR SHARE");
29091            }
29092        }
29093        if !e.expressions.is_empty() {
29094            self.write_keyword(" OF ");
29095            for (i, expr) in e.expressions.iter().enumerate() {
29096                if i > 0 {
29097                    self.write(", ");
29098                }
29099                self.generate_expression(expr)?;
29100            }
29101        }
29102        // Handle wait option following Python sqlglot convention:
29103        // - Boolean(true) -> NOWAIT
29104        // - Boolean(false) -> SKIP LOCKED
29105        // - Literal (number) -> WAIT n
29106        if let Some(wait) = &e.wait {
29107            match wait.as_ref() {
29108                Expression::Boolean(b) => {
29109                    if b.value {
29110                        self.write_keyword(" NOWAIT");
29111                    } else {
29112                        self.write_keyword(" SKIP LOCKED");
29113                    }
29114                }
29115                _ => {
29116                    // It's a literal (number), output WAIT n
29117                    self.write_keyword(" WAIT ");
29118                    self.generate_expression(wait)?;
29119                }
29120            }
29121        }
29122        Ok(())
29123    }
29124
29125    fn generate_lock_property(&mut self, e: &LockProperty) -> Result<()> {
29126        // LOCK property
29127        self.write_keyword("LOCK");
29128        self.write_space();
29129        self.generate_expression(&e.this)?;
29130        Ok(())
29131    }
29132
29133    fn generate_locking_property(&mut self, e: &LockingProperty) -> Result<()> {
29134        // Python: LOCKING kind [this] [for_or_in] lock_type [OVERRIDE]
29135        self.write_keyword("LOCKING");
29136        self.write_space();
29137        self.write(&e.kind);
29138        if let Some(this) = &e.this {
29139            self.write_space();
29140            self.generate_expression(this)?;
29141        }
29142        if let Some(for_or_in) = &e.for_or_in {
29143            self.write_space();
29144            self.generate_expression(for_or_in)?;
29145        }
29146        if let Some(lock_type) = &e.lock_type {
29147            self.write_space();
29148            self.generate_expression(lock_type)?;
29149        }
29150        if e.override_.is_some() {
29151            self.write_keyword(" OVERRIDE");
29152        }
29153        Ok(())
29154    }
29155
29156    fn generate_locking_statement(&mut self, e: &LockingStatement) -> Result<()> {
29157        // this expression
29158        self.generate_expression(&e.this)?;
29159        self.write_space();
29160        self.generate_expression(&e.expression)?;
29161        Ok(())
29162    }
29163
29164    fn generate_log_property(&mut self, e: &LogProperty) -> Result<()> {
29165        // [NO] LOG
29166        if e.no.is_some() {
29167            self.write_keyword("NO ");
29168        }
29169        self.write_keyword("LOG");
29170        Ok(())
29171    }
29172
29173    fn generate_md5_digest(&mut self, e: &MD5Digest) -> Result<()> {
29174        // MD5(this, expressions...)
29175        self.write_keyword("MD5");
29176        self.write("(");
29177        self.generate_expression(&e.this)?;
29178        for expr in &e.expressions {
29179            self.write(", ");
29180            self.generate_expression(expr)?;
29181        }
29182        self.write(")");
29183        Ok(())
29184    }
29185
29186    fn generate_ml_forecast(&mut self, e: &MLForecast) -> Result<()> {
29187        // ML.FORECAST(model, [params])
29188        self.write_keyword("ML.FORECAST");
29189        self.write("(");
29190        self.generate_expression(&e.this)?;
29191        if let Some(expression) = &e.expression {
29192            self.write(", ");
29193            self.generate_expression(expression)?;
29194        }
29195        if let Some(params) = &e.params_struct {
29196            self.write(", ");
29197            self.generate_expression(params)?;
29198        }
29199        self.write(")");
29200        Ok(())
29201    }
29202
29203    fn generate_ml_translate(&mut self, e: &MLTranslate) -> Result<()> {
29204        // ML.TRANSLATE(model, input, [params])
29205        self.write_keyword("ML.TRANSLATE");
29206        self.write("(");
29207        self.generate_expression(&e.this)?;
29208        self.write(", ");
29209        self.generate_expression(&e.expression)?;
29210        if let Some(params) = &e.params_struct {
29211            self.write(", ");
29212            self.generate_expression(params)?;
29213        }
29214        self.write(")");
29215        Ok(())
29216    }
29217
29218    fn generate_make_interval(&mut self, e: &MakeInterval) -> Result<()> {
29219        // MAKE_INTERVAL(years => x, months => y, ...)
29220        self.write_keyword("MAKE_INTERVAL");
29221        self.write("(");
29222        let mut first = true;
29223        if let Some(year) = &e.year {
29224            self.write("years => ");
29225            self.generate_expression(year)?;
29226            first = false;
29227        }
29228        if let Some(month) = &e.month {
29229            if !first {
29230                self.write(", ");
29231            }
29232            self.write("months => ");
29233            self.generate_expression(month)?;
29234            first = false;
29235        }
29236        if let Some(week) = &e.week {
29237            if !first {
29238                self.write(", ");
29239            }
29240            self.write("weeks => ");
29241            self.generate_expression(week)?;
29242            first = false;
29243        }
29244        if let Some(day) = &e.day {
29245            if !first {
29246                self.write(", ");
29247            }
29248            self.write("days => ");
29249            self.generate_expression(day)?;
29250            first = false;
29251        }
29252        if let Some(hour) = &e.hour {
29253            if !first {
29254                self.write(", ");
29255            }
29256            self.write("hours => ");
29257            self.generate_expression(hour)?;
29258            first = false;
29259        }
29260        if let Some(minute) = &e.minute {
29261            if !first {
29262                self.write(", ");
29263            }
29264            self.write("mins => ");
29265            self.generate_expression(minute)?;
29266            first = false;
29267        }
29268        if let Some(second) = &e.second {
29269            if !first {
29270                self.write(", ");
29271            }
29272            self.write("secs => ");
29273            self.generate_expression(second)?;
29274        }
29275        self.write(")");
29276        Ok(())
29277    }
29278
29279    fn generate_manhattan_distance(&mut self, e: &ManhattanDistance) -> Result<()> {
29280        // MANHATTAN_DISTANCE(vector1, vector2)
29281        self.write_keyword("MANHATTAN_DISTANCE");
29282        self.write("(");
29283        self.generate_expression(&e.this)?;
29284        self.write(", ");
29285        self.generate_expression(&e.expression)?;
29286        self.write(")");
29287        Ok(())
29288    }
29289
29290    fn generate_map(&mut self, e: &Map) -> Result<()> {
29291        // MAP(key1, value1, key2, value2, ...)
29292        self.write_keyword("MAP");
29293        self.write("(");
29294        for (i, (key, value)) in e.keys.iter().zip(e.values.iter()).enumerate() {
29295            if i > 0 {
29296                self.write(", ");
29297            }
29298            self.generate_expression(key)?;
29299            self.write(", ");
29300            self.generate_expression(value)?;
29301        }
29302        self.write(")");
29303        Ok(())
29304    }
29305
29306    fn generate_map_cat(&mut self, e: &MapCat) -> Result<()> {
29307        // MAP_CAT(map1, map2)
29308        self.write_keyword("MAP_CAT");
29309        self.write("(");
29310        self.generate_expression(&e.this)?;
29311        self.write(", ");
29312        self.generate_expression(&e.expression)?;
29313        self.write(")");
29314        Ok(())
29315    }
29316
29317    fn generate_map_delete(&mut self, e: &MapDelete) -> Result<()> {
29318        // MAP_DELETE(map, key1, key2, ...)
29319        self.write_keyword("MAP_DELETE");
29320        self.write("(");
29321        self.generate_expression(&e.this)?;
29322        for expr in &e.expressions {
29323            self.write(", ");
29324            self.generate_expression(expr)?;
29325        }
29326        self.write(")");
29327        Ok(())
29328    }
29329
29330    fn generate_map_insert(&mut self, e: &MapInsert) -> Result<()> {
29331        // MAP_INSERT(map, key, value, [update_flag])
29332        self.write_keyword("MAP_INSERT");
29333        self.write("(");
29334        self.generate_expression(&e.this)?;
29335        if let Some(key) = &e.key {
29336            self.write(", ");
29337            self.generate_expression(key)?;
29338        }
29339        if let Some(value) = &e.value {
29340            self.write(", ");
29341            self.generate_expression(value)?;
29342        }
29343        if let Some(update_flag) = &e.update_flag {
29344            self.write(", ");
29345            self.generate_expression(update_flag)?;
29346        }
29347        self.write(")");
29348        Ok(())
29349    }
29350
29351    fn generate_map_pick(&mut self, e: &MapPick) -> Result<()> {
29352        // MAP_PICK(map, key1, key2, ...)
29353        self.write_keyword("MAP_PICK");
29354        self.write("(");
29355        self.generate_expression(&e.this)?;
29356        for expr in &e.expressions {
29357            self.write(", ");
29358            self.generate_expression(expr)?;
29359        }
29360        self.write(")");
29361        Ok(())
29362    }
29363
29364    fn generate_masking_policy_column_constraint(
29365        &mut self,
29366        e: &MaskingPolicyColumnConstraint,
29367    ) -> Result<()> {
29368        // Python: MASKING POLICY name [USING (cols)]
29369        self.write_keyword("MASKING POLICY");
29370        self.write_space();
29371        self.generate_expression(&e.this)?;
29372        if !e.expressions.is_empty() {
29373            self.write_keyword(" USING");
29374            self.write(" (");
29375            for (i, expr) in e.expressions.iter().enumerate() {
29376                if i > 0 {
29377                    self.write(", ");
29378                }
29379                self.generate_expression(expr)?;
29380            }
29381            self.write(")");
29382        }
29383        Ok(())
29384    }
29385
29386    fn generate_match_against(&mut self, e: &MatchAgainst) -> Result<()> {
29387        if matches!(
29388            self.config.dialect,
29389            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
29390        ) {
29391            if e.expressions.len() > 1 {
29392                self.write("(");
29393            }
29394            for (i, expr) in e.expressions.iter().enumerate() {
29395                if i > 0 {
29396                    self.write_keyword(" OR ");
29397                }
29398                self.generate_expression(expr)?;
29399                self.write_space();
29400                self.write("@@");
29401                self.write_space();
29402                self.generate_expression(&e.this)?;
29403            }
29404            if e.expressions.len() > 1 {
29405                self.write(")");
29406            }
29407            return Ok(());
29408        }
29409
29410        // MATCH(columns) AGAINST(expr [modifier])
29411        self.write_keyword("MATCH");
29412        self.write("(");
29413        for (i, expr) in e.expressions.iter().enumerate() {
29414            if i > 0 {
29415                self.write(", ");
29416            }
29417            self.generate_expression(expr)?;
29418        }
29419        self.write(")");
29420        self.write_keyword(" AGAINST");
29421        self.write("(");
29422        self.generate_expression(&e.this)?;
29423        if let Some(modifier) = &e.modifier {
29424            self.write_space();
29425            self.generate_expression(modifier)?;
29426        }
29427        self.write(")");
29428        Ok(())
29429    }
29430
29431    fn generate_match_recognize_measure(&mut self, e: &MatchRecognizeMeasure) -> Result<()> {
29432        // Python: [window_frame] this
29433        if let Some(window_frame) = &e.window_frame {
29434            self.write(&format!("{:?}", window_frame).to_uppercase());
29435            self.write_space();
29436        }
29437        self.generate_expression(&e.this)?;
29438        Ok(())
29439    }
29440
29441    fn generate_materialized_property(&mut self, e: &MaterializedProperty) -> Result<()> {
29442        // MATERIALIZED [this]
29443        self.write_keyword("MATERIALIZED");
29444        if let Some(this) = &e.this {
29445            self.write_space();
29446            self.generate_expression(this)?;
29447        }
29448        Ok(())
29449    }
29450
29451    fn generate_merge(&mut self, e: &Merge) -> Result<()> {
29452        // MERGE INTO target USING source ON condition WHEN ...
29453        // DuckDB variant: MERGE INTO target USING source USING (key_columns) WHEN ...
29454        if let Some(with_) = &e.with_ {
29455            self.generate_expression(with_)?;
29456            self.write_space();
29457        }
29458        self.write_keyword("MERGE INTO");
29459        self.write_space();
29460        self.generate_expression(&e.this)?;
29461
29462        // USING clause - newline before in pretty mode
29463        if self.config.pretty {
29464            self.write_newline();
29465            self.write_indent();
29466        } else {
29467            self.write_space();
29468        }
29469        self.write_keyword("USING");
29470        self.write_space();
29471        self.generate_expression(&e.using)?;
29472
29473        // ON clause - newline before in pretty mode
29474        if let Some(on) = &e.on {
29475            if self.config.pretty {
29476                self.write_newline();
29477                self.write_indent();
29478            } else {
29479                self.write_space();
29480            }
29481            self.write_keyword("ON");
29482            self.write_space();
29483            self.generate_expression(on)?;
29484        }
29485        // DuckDB USING (key_columns) clause
29486        if let Some(using_cond) = &e.using_cond {
29487            self.write_space();
29488            self.write_keyword("USING");
29489            self.write_space();
29490            self.write("(");
29491            // using_cond is a Tuple containing the column identifiers
29492            if let Expression::Tuple(tuple) = using_cond.as_ref() {
29493                for (i, col) in tuple.expressions.iter().enumerate() {
29494                    if i > 0 {
29495                        self.write(", ");
29496                    }
29497                    self.generate_expression(col)?;
29498                }
29499            } else {
29500                self.generate_expression(using_cond)?;
29501            }
29502            self.write(")");
29503        }
29504        // For PostgreSQL dialect, extract target table name/alias to strip from UPDATE SET
29505        let saved_merge_strip = std::mem::take(&mut self.merge_strip_qualifiers);
29506        if matches!(
29507            self.config.dialect,
29508            Some(crate::DialectType::PostgreSQL)
29509                | Some(crate::DialectType::Redshift)
29510                | Some(crate::DialectType::Trino)
29511                | Some(crate::DialectType::Presto)
29512                | Some(crate::DialectType::Athena)
29513        ) {
29514            let mut names = Vec::new();
29515            match e.this.as_ref() {
29516                Expression::Alias(a) => {
29517                    // e.g., "x AS z" -> strip both "x" and "z"
29518                    if let Expression::Table(t) = &a.this {
29519                        names.push(t.name.name.clone());
29520                    } else if let Expression::Identifier(id) = &a.this {
29521                        names.push(id.name.clone());
29522                    }
29523                    names.push(a.alias.name.clone());
29524                }
29525                Expression::Table(t) => {
29526                    names.push(t.name.name.clone());
29527                }
29528                Expression::Identifier(id) => {
29529                    names.push(id.name.clone());
29530                }
29531                _ => {}
29532            }
29533            self.merge_strip_qualifiers = names;
29534        }
29535
29536        // WHEN clauses - newline before each in pretty mode
29537        if let Some(whens) = &e.whens {
29538            if self.config.pretty {
29539                self.write_newline();
29540                self.write_indent();
29541            } else {
29542                self.write_space();
29543            }
29544            self.generate_expression(whens)?;
29545        }
29546
29547        // Restore merge_strip_qualifiers
29548        self.merge_strip_qualifiers = saved_merge_strip;
29549
29550        // OUTPUT/RETURNING clause - newline before in pretty mode
29551        if let Some(returning) = &e.returning {
29552            if self.config.pretty {
29553                self.write_newline();
29554                self.write_indent();
29555            } else {
29556                self.write_space();
29557            }
29558            self.generate_expression(returning)?;
29559        }
29560        Ok(())
29561    }
29562
29563    fn generate_merge_block_ratio_property(&mut self, e: &MergeBlockRatioProperty) -> Result<()> {
29564        // Python: NO MERGEBLOCKRATIO | DEFAULT MERGEBLOCKRATIO | MERGEBLOCKRATIO=this [PERCENT]
29565        if e.no.is_some() {
29566            self.write_keyword("NO MERGEBLOCKRATIO");
29567        } else if e.default.is_some() {
29568            self.write_keyword("DEFAULT MERGEBLOCKRATIO");
29569        } else {
29570            self.write_keyword("MERGEBLOCKRATIO");
29571            self.write("=");
29572            if let Some(this) = &e.this {
29573                self.generate_expression(this)?;
29574            }
29575            if e.percent.is_some() {
29576                self.write_keyword(" PERCENT");
29577            }
29578        }
29579        Ok(())
29580    }
29581
29582    fn generate_merge_tree_ttl(&mut self, e: &MergeTreeTTL) -> Result<()> {
29583        // TTL expressions [WHERE where] [GROUP BY group] [SET aggregates]
29584        self.write_keyword("TTL");
29585        let pretty_clickhouse = self.config.pretty
29586            && matches!(
29587                self.config.dialect,
29588                Some(crate::dialects::DialectType::ClickHouse)
29589            );
29590
29591        if pretty_clickhouse {
29592            self.write_newline();
29593            self.indent_level += 1;
29594            for (i, expr) in e.expressions.iter().enumerate() {
29595                if i > 0 {
29596                    self.write(",");
29597                    self.write_newline();
29598                }
29599                self.write_indent();
29600                self.generate_expression(expr)?;
29601            }
29602            self.indent_level -= 1;
29603        } else {
29604            self.write_space();
29605            for (i, expr) in e.expressions.iter().enumerate() {
29606                if i > 0 {
29607                    self.write(", ");
29608                }
29609                self.generate_expression(expr)?;
29610            }
29611        }
29612
29613        if let Some(where_) = &e.where_ {
29614            if pretty_clickhouse {
29615                self.write_newline();
29616                if let Expression::Where(w) = where_.as_ref() {
29617                    self.write_indent();
29618                    self.write_keyword("WHERE");
29619                    self.write_newline();
29620                    self.indent_level += 1;
29621                    self.write_indent();
29622                    self.generate_expression(&w.this)?;
29623                    self.indent_level -= 1;
29624                } else {
29625                    self.write_indent();
29626                    self.generate_expression(where_)?;
29627                }
29628            } else {
29629                self.write_space();
29630                self.generate_expression(where_)?;
29631            }
29632        }
29633        if let Some(group) = &e.group {
29634            if pretty_clickhouse {
29635                self.write_newline();
29636                if let Expression::Group(g) = group.as_ref() {
29637                    self.write_indent();
29638                    self.write_keyword("GROUP BY");
29639                    self.write_newline();
29640                    self.indent_level += 1;
29641                    for (i, expr) in g.expressions.iter().enumerate() {
29642                        if i > 0 {
29643                            self.write(",");
29644                            self.write_newline();
29645                        }
29646                        self.write_indent();
29647                        self.generate_expression(expr)?;
29648                    }
29649                    self.indent_level -= 1;
29650                } else {
29651                    self.write_indent();
29652                    self.generate_expression(group)?;
29653                }
29654            } else {
29655                self.write_space();
29656                self.generate_expression(group)?;
29657            }
29658        }
29659        if let Some(aggregates) = &e.aggregates {
29660            if pretty_clickhouse {
29661                self.write_newline();
29662                self.write_indent();
29663                self.write_keyword("SET");
29664                self.write_newline();
29665                self.indent_level += 1;
29666                if let Expression::Tuple(t) = aggregates.as_ref() {
29667                    for (i, agg) in t.expressions.iter().enumerate() {
29668                        if i > 0 {
29669                            self.write(",");
29670                            self.write_newline();
29671                        }
29672                        self.write_indent();
29673                        self.generate_expression(agg)?;
29674                    }
29675                } else {
29676                    self.write_indent();
29677                    self.generate_expression(aggregates)?;
29678                }
29679                self.indent_level -= 1;
29680            } else {
29681                self.write_space();
29682                self.write_keyword("SET");
29683                self.write_space();
29684                self.generate_expression(aggregates)?;
29685            }
29686        }
29687        Ok(())
29688    }
29689
29690    fn generate_merge_tree_ttl_action(&mut self, e: &MergeTreeTTLAction) -> Result<()> {
29691        // Python: this [DELETE] [RECOMPRESS codec] [TO DISK disk] [TO VOLUME volume]
29692        self.generate_expression(&e.this)?;
29693        if e.delete.is_some() {
29694            self.write_keyword(" DELETE");
29695        }
29696        if let Some(recompress) = &e.recompress {
29697            self.write_keyword(" RECOMPRESS ");
29698            self.generate_expression(recompress)?;
29699        }
29700        if let Some(to_disk) = &e.to_disk {
29701            self.write_keyword(" TO DISK ");
29702            self.generate_expression(to_disk)?;
29703        }
29704        if let Some(to_volume) = &e.to_volume {
29705            self.write_keyword(" TO VOLUME ");
29706            self.generate_expression(to_volume)?;
29707        }
29708        Ok(())
29709    }
29710
29711    fn generate_minhash(&mut self, e: &Minhash) -> Result<()> {
29712        // MINHASH(this, expressions...)
29713        self.write_keyword("MINHASH");
29714        self.write("(");
29715        self.generate_expression(&e.this)?;
29716        for expr in &e.expressions {
29717            self.write(", ");
29718            self.generate_expression(expr)?;
29719        }
29720        self.write(")");
29721        Ok(())
29722    }
29723
29724    fn generate_model_attribute(&mut self, e: &ModelAttribute) -> Result<()> {
29725        // model!attribute - Snowflake syntax
29726        self.generate_expression(&e.this)?;
29727        self.write("!");
29728        self.generate_expression(&e.expression)?;
29729        Ok(())
29730    }
29731
29732    fn generate_monthname(&mut self, e: &Monthname) -> Result<()> {
29733        // MONTHNAME(this)
29734        self.write_keyword("MONTHNAME");
29735        self.write("(");
29736        self.generate_expression(&e.this)?;
29737        self.write(")");
29738        Ok(())
29739    }
29740
29741    fn generate_multitable_inserts(&mut self, e: &MultitableInserts) -> Result<()> {
29742        // Output leading comments
29743        for comment in &e.leading_comments {
29744            self.write_formatted_comment(comment);
29745            if self.config.pretty {
29746                self.write_newline();
29747                self.write_indent();
29748            } else {
29749                self.write_space();
29750            }
29751        }
29752        // Python: INSERT kind expressions source
29753        self.write_keyword("INSERT");
29754        self.write_space();
29755        self.write(&e.kind);
29756        if self.config.pretty {
29757            self.indent_level += 1;
29758            for expr in &e.expressions {
29759                self.write_newline();
29760                self.write_indent();
29761                self.generate_expression(expr)?;
29762            }
29763            self.indent_level -= 1;
29764        } else {
29765            for expr in &e.expressions {
29766                self.write_space();
29767                self.generate_expression(expr)?;
29768            }
29769        }
29770        if let Some(source) = &e.source {
29771            if self.config.pretty {
29772                self.write_newline();
29773                self.write_indent();
29774            } else {
29775                self.write_space();
29776            }
29777            self.generate_expression(source)?;
29778        }
29779        Ok(())
29780    }
29781
29782    fn generate_next_value_for(&mut self, e: &NextValueFor) -> Result<()> {
29783        // Python: NEXT VALUE FOR this [OVER (order)]
29784        self.write_keyword("NEXT VALUE FOR");
29785        self.write_space();
29786        self.generate_expression(&e.this)?;
29787        if let Some(order) = &e.order {
29788            self.write_space();
29789            self.write_keyword("OVER");
29790            self.write(" (");
29791            self.generate_expression(order)?;
29792            self.write(")");
29793        }
29794        Ok(())
29795    }
29796
29797    fn generate_normal(&mut self, e: &Normal) -> Result<()> {
29798        // NORMAL(mean, stddev, gen)
29799        self.write_keyword("NORMAL");
29800        self.write("(");
29801        self.generate_expression(&e.this)?;
29802        if let Some(stddev) = &e.stddev {
29803            self.write(", ");
29804            self.generate_expression(stddev)?;
29805        }
29806        if let Some(gen) = &e.gen {
29807            self.write(", ");
29808            self.generate_expression(gen)?;
29809        }
29810        self.write(")");
29811        Ok(())
29812    }
29813
29814    fn generate_normalize(&mut self, e: &Normalize) -> Result<()> {
29815        // NORMALIZE(this, form) or CASEFOLD version
29816        if e.is_casefold.is_some() {
29817            self.write_keyword("NORMALIZE_AND_CASEFOLD");
29818        } else {
29819            self.write_keyword("NORMALIZE");
29820        }
29821        self.write("(");
29822        self.generate_expression(&e.this)?;
29823        if let Some(form) = &e.form {
29824            self.write(", ");
29825            self.generate_expression(form)?;
29826        }
29827        self.write(")");
29828        Ok(())
29829    }
29830
29831    fn generate_not_null_column_constraint(&mut self, e: &NotNullColumnConstraint) -> Result<()> {
29832        // Python: [NOT ]NULL
29833        if e.allow_null.is_none() {
29834            self.write_keyword("NOT ");
29835        }
29836        self.write_keyword("NULL");
29837        Ok(())
29838    }
29839
29840    fn generate_nullif(&mut self, e: &Nullif) -> Result<()> {
29841        // NULLIF(this, expression)
29842        self.write_keyword("NULLIF");
29843        self.write("(");
29844        self.generate_expression(&e.this)?;
29845        self.write(", ");
29846        self.generate_expression(&e.expression)?;
29847        self.write(")");
29848        Ok(())
29849    }
29850
29851    fn generate_number_to_str(&mut self, e: &NumberToStr) -> Result<()> {
29852        // FORMAT(this, format, culture)
29853        self.write_keyword("FORMAT");
29854        self.write("(");
29855        self.generate_expression(&e.this)?;
29856        self.write(", '");
29857        self.write(&e.format);
29858        self.write("'");
29859        if let Some(culture) = &e.culture {
29860            self.write(", ");
29861            self.generate_expression(culture)?;
29862        }
29863        self.write(")");
29864        Ok(())
29865    }
29866
29867    fn generate_object_agg(&mut self, e: &ObjectAgg) -> Result<()> {
29868        // OBJECT_AGG(key, value)
29869        self.write_keyword("OBJECT_AGG");
29870        self.write("(");
29871        self.generate_expression(&e.this)?;
29872        self.write(", ");
29873        self.generate_expression(&e.expression)?;
29874        self.write(")");
29875        Ok(())
29876    }
29877
29878    fn generate_object_identifier(&mut self, e: &ObjectIdentifier) -> Result<()> {
29879        // Python: Just returns the name
29880        self.generate_expression(&e.this)?;
29881        Ok(())
29882    }
29883
29884    fn generate_object_insert(&mut self, e: &ObjectInsert) -> Result<()> {
29885        // OBJECT_INSERT(obj, key, value, [update_flag])
29886        self.write_keyword("OBJECT_INSERT");
29887        self.write("(");
29888        self.generate_expression(&e.this)?;
29889        if let Some(key) = &e.key {
29890            self.write(", ");
29891            self.generate_expression(key)?;
29892        }
29893        if let Some(value) = &e.value {
29894            self.write(", ");
29895            self.generate_expression(value)?;
29896        }
29897        if let Some(update_flag) = &e.update_flag {
29898            self.write(", ");
29899            self.generate_expression(update_flag)?;
29900        }
29901        self.write(")");
29902        Ok(())
29903    }
29904
29905    fn generate_offset(&mut self, e: &Offset) -> Result<()> {
29906        // OFFSET value [ROW|ROWS]
29907        self.write_keyword("OFFSET");
29908        self.write_space();
29909        self.generate_expression(&e.this)?;
29910        // Output ROWS keyword only for TSQL/Oracle targets
29911        if e.rows == Some(true)
29912            && matches!(
29913                self.config.dialect,
29914                Some(crate::dialects::DialectType::TSQL)
29915                    | Some(crate::dialects::DialectType::Oracle)
29916            )
29917        {
29918            self.write_space();
29919            self.write_keyword("ROWS");
29920        }
29921        Ok(())
29922    }
29923
29924    fn generate_qualify(&mut self, e: &Qualify) -> Result<()> {
29925        // QUALIFY condition (Snowflake/BigQuery)
29926        self.write_keyword("QUALIFY");
29927        self.write_space();
29928        self.generate_expression(&e.this)?;
29929        Ok(())
29930    }
29931
29932    fn generate_on_cluster(&mut self, e: &OnCluster) -> Result<()> {
29933        // ON CLUSTER cluster_name
29934        self.write_keyword("ON CLUSTER");
29935        self.write_space();
29936        self.generate_expression(&e.this)?;
29937        Ok(())
29938    }
29939
29940    fn generate_on_commit_property(&mut self, e: &OnCommitProperty) -> Result<()> {
29941        // ON COMMIT [DELETE ROWS | PRESERVE ROWS]
29942        self.write_keyword("ON COMMIT");
29943        if e.delete.is_some() {
29944            self.write_keyword(" DELETE ROWS");
29945        } else {
29946            self.write_keyword(" PRESERVE ROWS");
29947        }
29948        Ok(())
29949    }
29950
29951    fn generate_on_condition(&mut self, e: &OnCondition) -> Result<()> {
29952        // Python: error/empty/null handling
29953        if let Some(empty) = &e.empty {
29954            self.generate_expression(empty)?;
29955            self.write_keyword(" ON EMPTY");
29956        }
29957        if let Some(error) = &e.error {
29958            if e.empty.is_some() {
29959                self.write_space();
29960            }
29961            self.generate_expression(error)?;
29962            self.write_keyword(" ON ERROR");
29963        }
29964        if let Some(null) = &e.null {
29965            if e.empty.is_some() || e.error.is_some() {
29966                self.write_space();
29967            }
29968            self.generate_expression(null)?;
29969            self.write_keyword(" ON NULL");
29970        }
29971        Ok(())
29972    }
29973
29974    fn generate_on_conflict(&mut self, e: &OnConflict) -> Result<()> {
29975        // Materialize doesn't support ON CONFLICT - skip entirely
29976        if matches!(self.config.dialect, Some(DialectType::Materialize)) {
29977            return Ok(());
29978        }
29979        // Python: ON CONFLICT|ON DUPLICATE KEY [ON CONSTRAINT constraint] [conflict_keys] action
29980        if e.duplicate.is_some() {
29981            // MySQL: ON DUPLICATE KEY UPDATE col = val, ...
29982            self.write_keyword("ON DUPLICATE KEY UPDATE");
29983            for (i, expr) in e.expressions.iter().enumerate() {
29984                if i > 0 {
29985                    self.write(",");
29986                }
29987                self.write_space();
29988                self.generate_expression(expr)?;
29989            }
29990            return Ok(());
29991        } else {
29992            self.write_keyword("ON CONFLICT");
29993        }
29994        if let Some(constraint) = &e.constraint {
29995            self.write_keyword(" ON CONSTRAINT ");
29996            self.generate_expression(constraint)?;
29997        }
29998        if let Some(conflict_keys) = &e.conflict_keys {
29999            // conflict_keys can be a Tuple containing expressions
30000            if let Expression::Tuple(t) = conflict_keys.as_ref() {
30001                self.write("(");
30002                for (i, expr) in t.expressions.iter().enumerate() {
30003                    if i > 0 {
30004                        self.write(", ");
30005                    }
30006                    self.generate_expression(expr)?;
30007                }
30008                self.write(")");
30009            } else {
30010                self.write("(");
30011                self.generate_expression(conflict_keys)?;
30012                self.write(")");
30013            }
30014        }
30015        if let Some(index_predicate) = &e.index_predicate {
30016            self.write_keyword(" WHERE ");
30017            self.generate_expression(index_predicate)?;
30018        }
30019        if let Some(action) = &e.action {
30020            // Check if action is "NOTHING" or an UPDATE set
30021            if let Expression::Identifier(id) = action.as_ref() {
30022                if id.name == "NOTHING" || id.name.to_uppercase() == "NOTHING" {
30023                    self.write_keyword(" DO NOTHING");
30024                } else {
30025                    self.write_keyword(" DO ");
30026                    self.generate_expression(action)?;
30027                }
30028            } else if let Expression::Tuple(t) = action.as_ref() {
30029                // DO UPDATE SET col1 = val1, col2 = val2
30030                self.write_keyword(" DO UPDATE SET ");
30031                for (i, expr) in t.expressions.iter().enumerate() {
30032                    if i > 0 {
30033                        self.write(", ");
30034                    }
30035                    self.generate_expression(expr)?;
30036                }
30037            } else {
30038                self.write_keyword(" DO ");
30039                self.generate_expression(action)?;
30040            }
30041        }
30042        // WHERE clause for the UPDATE action
30043        if let Some(where_) = &e.where_ {
30044            self.write_keyword(" WHERE ");
30045            self.generate_expression(where_)?;
30046        }
30047        Ok(())
30048    }
30049
30050    fn generate_on_property(&mut self, e: &OnProperty) -> Result<()> {
30051        // ON property_value
30052        self.write_keyword("ON");
30053        self.write_space();
30054        self.generate_expression(&e.this)?;
30055        Ok(())
30056    }
30057
30058    fn generate_opclass(&mut self, e: &Opclass) -> Result<()> {
30059        // Python: this expression (e.g., column opclass)
30060        self.generate_expression(&e.this)?;
30061        self.write_space();
30062        self.generate_expression(&e.expression)?;
30063        Ok(())
30064    }
30065
30066    fn generate_open_json(&mut self, e: &OpenJSON) -> Result<()> {
30067        // Python: OPENJSON(this[, path]) [WITH (columns)]
30068        self.write_keyword("OPENJSON");
30069        self.write("(");
30070        self.generate_expression(&e.this)?;
30071        if let Some(path) = &e.path {
30072            self.write(", ");
30073            self.generate_expression(path)?;
30074        }
30075        self.write(")");
30076        if !e.expressions.is_empty() {
30077            self.write_keyword(" WITH");
30078            if self.config.pretty {
30079                self.write(" (\n");
30080                self.indent_level += 2;
30081                for (i, expr) in e.expressions.iter().enumerate() {
30082                    if i > 0 {
30083                        self.write(",\n");
30084                    }
30085                    self.write_indent();
30086                    self.generate_expression(expr)?;
30087                }
30088                self.write("\n");
30089                self.indent_level -= 2;
30090                self.write(")");
30091            } else {
30092                self.write(" (");
30093                for (i, expr) in e.expressions.iter().enumerate() {
30094                    if i > 0 {
30095                        self.write(", ");
30096                    }
30097                    self.generate_expression(expr)?;
30098                }
30099                self.write(")");
30100            }
30101        }
30102        Ok(())
30103    }
30104
30105    fn generate_open_json_column_def(&mut self, e: &OpenJSONColumnDef) -> Result<()> {
30106        // Python: this kind [path] [AS JSON]
30107        self.generate_expression(&e.this)?;
30108        self.write_space();
30109        // Use parsed data_type if available, otherwise fall back to kind string
30110        if let Some(ref dt) = e.data_type {
30111            self.generate_data_type(dt)?;
30112        } else if !e.kind.is_empty() {
30113            self.write(&e.kind);
30114        }
30115        if let Some(path) = &e.path {
30116            self.write_space();
30117            self.generate_expression(path)?;
30118        }
30119        if e.as_json.is_some() {
30120            self.write_keyword(" AS JSON");
30121        }
30122        Ok(())
30123    }
30124
30125    fn generate_operator(&mut self, e: &Operator) -> Result<()> {
30126        // this OPERATOR(op) expression
30127        self.generate_expression(&e.this)?;
30128        self.write_space();
30129        if let Some(op) = &e.operator {
30130            self.write_keyword("OPERATOR");
30131            self.write("(");
30132            self.generate_expression(op)?;
30133            self.write(")");
30134        }
30135        // Emit inline comments between OPERATOR() and the RHS
30136        for comment in &e.comments {
30137            self.write_space();
30138            self.write_formatted_comment(comment);
30139        }
30140        self.write_space();
30141        self.generate_expression(&e.expression)?;
30142        Ok(())
30143    }
30144
30145    fn generate_order_by(&mut self, e: &OrderBy) -> Result<()> {
30146        // ORDER BY expr1 [ASC|DESC] [NULLS FIRST|LAST], expr2 ...
30147        self.write_keyword("ORDER BY");
30148        let pretty_clickhouse_single_paren = self.config.pretty
30149            && matches!(self.config.dialect, Some(DialectType::ClickHouse))
30150            && e.expressions.len() == 1
30151            && matches!(e.expressions[0].this, Expression::Paren(ref p) if !matches!(p.this, Expression::Tuple(_)));
30152        let clickhouse_single_tuple = matches!(self.config.dialect, Some(DialectType::ClickHouse))
30153            && e.expressions.len() == 1
30154            && matches!(e.expressions[0].this, Expression::Tuple(_))
30155            && !e.expressions[0].desc
30156            && e.expressions[0].nulls_first.is_none();
30157
30158        if pretty_clickhouse_single_paren {
30159            self.write_space();
30160            if let Expression::Paren(p) = &e.expressions[0].this {
30161                self.write("(");
30162                self.write_newline();
30163                self.indent_level += 1;
30164                self.write_indent();
30165                self.generate_expression(&p.this)?;
30166                self.indent_level -= 1;
30167                self.write_newline();
30168                self.write(")");
30169            }
30170            return Ok(());
30171        }
30172
30173        if clickhouse_single_tuple {
30174            self.write_space();
30175            if let Expression::Tuple(t) = &e.expressions[0].this {
30176                self.write("(");
30177                for (i, expr) in t.expressions.iter().enumerate() {
30178                    if i > 0 {
30179                        self.write(", ");
30180                    }
30181                    self.generate_expression(expr)?;
30182                }
30183                self.write(")");
30184            }
30185            return Ok(());
30186        }
30187
30188        self.write_space();
30189        for (i, ordered) in e.expressions.iter().enumerate() {
30190            if i > 0 {
30191                self.write(", ");
30192            }
30193            self.generate_expression(&ordered.this)?;
30194            if ordered.desc {
30195                self.write_space();
30196                self.write_keyword("DESC");
30197            } else if ordered.explicit_asc {
30198                self.write_space();
30199                self.write_keyword("ASC");
30200            }
30201            if let Some(nulls_first) = ordered.nulls_first {
30202                // In Dremio, NULLS LAST is the default, so skip generating it
30203                let skip_nulls_last =
30204                    !nulls_first && matches!(self.config.dialect, Some(DialectType::Dremio));
30205                if !skip_nulls_last {
30206                    self.write_space();
30207                    self.write_keyword("NULLS");
30208                    self.write_space();
30209                    if nulls_first {
30210                        self.write_keyword("FIRST");
30211                    } else {
30212                        self.write_keyword("LAST");
30213                    }
30214                }
30215            }
30216        }
30217        Ok(())
30218    }
30219
30220    fn generate_output_model_property(&mut self, e: &OutputModelProperty) -> Result<()> {
30221        // OUTPUT(model)
30222        self.write_keyword("OUTPUT");
30223        self.write("(");
30224        if self.config.pretty {
30225            self.indent_level += 1;
30226            self.write_newline();
30227            self.write_indent();
30228            self.generate_expression(&e.this)?;
30229            self.indent_level -= 1;
30230            self.write_newline();
30231        } else {
30232            self.generate_expression(&e.this)?;
30233        }
30234        self.write(")");
30235        Ok(())
30236    }
30237
30238    fn generate_overflow_truncate_behavior(&mut self, e: &OverflowTruncateBehavior) -> Result<()> {
30239        // Python: TRUNCATE [filler] WITH|WITHOUT COUNT
30240        self.write_keyword("TRUNCATE");
30241        if let Some(this) = &e.this {
30242            self.write_space();
30243            self.generate_expression(this)?;
30244        }
30245        if e.with_count.is_some() {
30246            self.write_keyword(" WITH COUNT");
30247        } else {
30248            self.write_keyword(" WITHOUT COUNT");
30249        }
30250        Ok(())
30251    }
30252
30253    fn generate_parameterized_agg(&mut self, e: &ParameterizedAgg) -> Result<()> {
30254        // Python: name(expressions)(params)
30255        self.generate_expression(&e.this)?;
30256        self.write("(");
30257        for (i, expr) in e.expressions.iter().enumerate() {
30258            if i > 0 {
30259                self.write(", ");
30260            }
30261            self.generate_expression(expr)?;
30262        }
30263        self.write(")(");
30264        for (i, param) in e.params.iter().enumerate() {
30265            if i > 0 {
30266                self.write(", ");
30267            }
30268            self.generate_expression(param)?;
30269        }
30270        self.write(")");
30271        Ok(())
30272    }
30273
30274    fn generate_parse_datetime(&mut self, e: &ParseDatetime) -> Result<()> {
30275        // PARSE_DATETIME(format, this) or similar
30276        self.write_keyword("PARSE_DATETIME");
30277        self.write("(");
30278        if let Some(format) = &e.format {
30279            self.write("'");
30280            self.write(format);
30281            self.write("', ");
30282        }
30283        self.generate_expression(&e.this)?;
30284        if let Some(zone) = &e.zone {
30285            self.write(", ");
30286            self.generate_expression(zone)?;
30287        }
30288        self.write(")");
30289        Ok(())
30290    }
30291
30292    fn generate_parse_ip(&mut self, e: &ParseIp) -> Result<()> {
30293        // PARSE_IP(this, type, permissive)
30294        self.write_keyword("PARSE_IP");
30295        self.write("(");
30296        self.generate_expression(&e.this)?;
30297        if let Some(type_) = &e.type_ {
30298            self.write(", ");
30299            self.generate_expression(type_)?;
30300        }
30301        if let Some(permissive) = &e.permissive {
30302            self.write(", ");
30303            self.generate_expression(permissive)?;
30304        }
30305        self.write(")");
30306        Ok(())
30307    }
30308
30309    fn generate_parse_json(&mut self, e: &ParseJSON) -> Result<()> {
30310        // PARSE_JSON(this, [expression])
30311        self.write_keyword("PARSE_JSON");
30312        self.write("(");
30313        self.generate_expression(&e.this)?;
30314        if let Some(expression) = &e.expression {
30315            self.write(", ");
30316            self.generate_expression(expression)?;
30317        }
30318        self.write(")");
30319        Ok(())
30320    }
30321
30322    fn generate_parse_time(&mut self, e: &ParseTime) -> Result<()> {
30323        // PARSE_TIME(format, this) or STR_TO_TIME(this, format)
30324        self.write_keyword("PARSE_TIME");
30325        self.write("(");
30326        self.write(&format!("'{}'", e.format));
30327        self.write(", ");
30328        self.generate_expression(&e.this)?;
30329        self.write(")");
30330        Ok(())
30331    }
30332
30333    fn generate_parse_url(&mut self, e: &ParseUrl) -> Result<()> {
30334        // PARSE_URL(this, [part_to_extract], [key], [permissive])
30335        self.write_keyword("PARSE_URL");
30336        self.write("(");
30337        self.generate_expression(&e.this)?;
30338        if let Some(part) = &e.part_to_extract {
30339            self.write(", ");
30340            self.generate_expression(part)?;
30341        }
30342        if let Some(key) = &e.key {
30343            self.write(", ");
30344            self.generate_expression(key)?;
30345        }
30346        if let Some(permissive) = &e.permissive {
30347            self.write(", ");
30348            self.generate_expression(permissive)?;
30349        }
30350        self.write(")");
30351        Ok(())
30352    }
30353
30354    fn generate_partition_expr(&mut self, e: &Partition) -> Result<()> {
30355        // PARTITION(expr1, expr2, ...) or SUBPARTITION(expr1, expr2, ...)
30356        if e.subpartition {
30357            self.write_keyword("SUBPARTITION");
30358        } else {
30359            self.write_keyword("PARTITION");
30360        }
30361        self.write("(");
30362        for (i, expr) in e.expressions.iter().enumerate() {
30363            if i > 0 {
30364                self.write(", ");
30365            }
30366            self.generate_expression(expr)?;
30367        }
30368        self.write(")");
30369        Ok(())
30370    }
30371
30372    fn generate_partition_bound_spec(&mut self, e: &PartitionBoundSpec) -> Result<()> {
30373        // IN (values) or WITH (MODULUS this, REMAINDER expression) or FROM (from) TO (to)
30374        if let Some(this) = &e.this {
30375            if let Some(expression) = &e.expression {
30376                // WITH (MODULUS this, REMAINDER expression)
30377                self.write_keyword("WITH");
30378                self.write(" (");
30379                self.write_keyword("MODULUS");
30380                self.write_space();
30381                self.generate_expression(this)?;
30382                self.write(", ");
30383                self.write_keyword("REMAINDER");
30384                self.write_space();
30385                self.generate_expression(expression)?;
30386                self.write(")");
30387            } else {
30388                // IN (this) - this could be a list
30389                self.write_keyword("IN");
30390                self.write(" (");
30391                self.generate_partition_bound_values(this)?;
30392                self.write(")");
30393            }
30394        } else if let (Some(from), Some(to)) = (&e.from_expressions, &e.to_expressions) {
30395            // FROM (from_expressions) TO (to_expressions)
30396            self.write_keyword("FROM");
30397            self.write(" (");
30398            self.generate_partition_bound_values(from)?;
30399            self.write(") ");
30400            self.write_keyword("TO");
30401            self.write(" (");
30402            self.generate_partition_bound_values(to)?;
30403            self.write(")");
30404        }
30405        Ok(())
30406    }
30407
30408    /// Generate partition bound values - handles Tuple expressions by outputting
30409    /// contents without wrapping parens (since caller provides the parens)
30410    fn generate_partition_bound_values(&mut self, expr: &Expression) -> Result<()> {
30411        if let Expression::Tuple(t) = expr {
30412            for (i, e) in t.expressions.iter().enumerate() {
30413                if i > 0 {
30414                    self.write(", ");
30415                }
30416                self.generate_expression(e)?;
30417            }
30418            Ok(())
30419        } else {
30420            self.generate_expression(expr)
30421        }
30422    }
30423
30424    fn generate_partition_by_list_property(&mut self, e: &PartitionByListProperty) -> Result<()> {
30425        // PARTITION BY LIST (partition_expressions) (create_expressions)
30426        self.write_keyword("PARTITION BY LIST");
30427        if let Some(partition_exprs) = &e.partition_expressions {
30428            self.write(" (");
30429            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
30430            self.generate_doris_partition_expressions(partition_exprs)?;
30431            self.write(")");
30432        }
30433        if let Some(create_exprs) = &e.create_expressions {
30434            self.write(" (");
30435            // Unwrap Tuple for partition definitions
30436            self.generate_doris_partition_definitions(create_exprs)?;
30437            self.write(")");
30438        }
30439        Ok(())
30440    }
30441
30442    fn generate_partition_by_range_property(&mut self, e: &PartitionByRangeProperty) -> Result<()> {
30443        // PARTITION BY RANGE (partition_expressions) (create_expressions)
30444        self.write_keyword("PARTITION BY RANGE");
30445        if let Some(partition_exprs) = &e.partition_expressions {
30446            self.write(" (");
30447            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
30448            self.generate_doris_partition_expressions(partition_exprs)?;
30449            self.write(")");
30450        }
30451        if let Some(create_exprs) = &e.create_expressions {
30452            self.write(" (");
30453            // Check for dynamic partition (PartitionByRangePropertyDynamic) or static (Tuple of Partition)
30454            self.generate_doris_partition_definitions(create_exprs)?;
30455            self.write(")");
30456        }
30457        Ok(())
30458    }
30459
30460    /// Generate Doris partition column expressions (unwrap Tuple)
30461    fn generate_doris_partition_expressions(&mut self, expr: &Expression) -> Result<()> {
30462        if let Expression::Tuple(t) = expr {
30463            for (i, e) in t.expressions.iter().enumerate() {
30464                if i > 0 {
30465                    self.write(", ");
30466                }
30467                self.generate_expression(e)?;
30468            }
30469        } else {
30470            self.generate_expression(expr)?;
30471        }
30472        Ok(())
30473    }
30474
30475    /// Generate Doris partition definitions (comma-separated Partition expressions)
30476    fn generate_doris_partition_definitions(&mut self, expr: &Expression) -> Result<()> {
30477        match expr {
30478            Expression::Tuple(t) => {
30479                // Multiple partitions, comma-separated
30480                for (i, part) in t.expressions.iter().enumerate() {
30481                    if i > 0 {
30482                        self.write(", ");
30483                    }
30484                    // For Partition expressions, generate the inner PartitionRange/PartitionList directly
30485                    if let Expression::Partition(p) = part {
30486                        for (j, inner) in p.expressions.iter().enumerate() {
30487                            if j > 0 {
30488                                self.write(", ");
30489                            }
30490                            self.generate_expression(inner)?;
30491                        }
30492                    } else {
30493                        self.generate_expression(part)?;
30494                    }
30495                }
30496            }
30497            Expression::PartitionByRangePropertyDynamic(_) => {
30498                // Dynamic partition - FROM/TO/INTERVAL
30499                self.generate_expression(expr)?;
30500            }
30501            _ => {
30502                self.generate_expression(expr)?;
30503            }
30504        }
30505        Ok(())
30506    }
30507
30508    fn generate_partition_by_range_property_dynamic(
30509        &mut self,
30510        e: &PartitionByRangePropertyDynamic,
30511    ) -> Result<()> {
30512        if e.use_start_end {
30513            // StarRocks: START ('val') END ('val') EVERY (expr)
30514            if let Some(start) = &e.start {
30515                self.write_keyword("START");
30516                self.write(" (");
30517                self.generate_expression(start)?;
30518                self.write(")");
30519            }
30520            if let Some(end) = &e.end {
30521                self.write_space();
30522                self.write_keyword("END");
30523                self.write(" (");
30524                self.generate_expression(end)?;
30525                self.write(")");
30526            }
30527            if let Some(every) = &e.every {
30528                self.write_space();
30529                self.write_keyword("EVERY");
30530                self.write(" (");
30531                // Use unquoted interval format for StarRocks
30532                self.generate_doris_interval(every)?;
30533                self.write(")");
30534            }
30535        } else {
30536            // Doris: FROM (start) TO (end) INTERVAL n UNIT
30537            if let Some(start) = &e.start {
30538                self.write_keyword("FROM");
30539                self.write(" (");
30540                self.generate_expression(start)?;
30541                self.write(")");
30542            }
30543            if let Some(end) = &e.end {
30544                self.write_space();
30545                self.write_keyword("TO");
30546                self.write(" (");
30547                self.generate_expression(end)?;
30548                self.write(")");
30549            }
30550            if let Some(every) = &e.every {
30551                self.write_space();
30552                // Generate INTERVAL n UNIT (not quoted, for Doris dynamic partition)
30553                self.generate_doris_interval(every)?;
30554            }
30555        }
30556        Ok(())
30557    }
30558
30559    /// Generate Doris-style interval without quoting numbers: INTERVAL n UNIT
30560    fn generate_doris_interval(&mut self, expr: &Expression) -> Result<()> {
30561        if let Expression::Interval(interval) = expr {
30562            self.write_keyword("INTERVAL");
30563            if let Some(ref value) = interval.this {
30564                self.write_space();
30565                // If the value is a string literal that looks like a number,
30566                // output it without quotes (matching Python sqlglot's
30567                // partitionbyrangepropertydynamic_sql which converts back to number)
30568                match value {
30569                    Expression::Literal(Literal::String(s))
30570                        if s.chars()
30571                            .all(|c| c.is_ascii_digit() || c == '.' || c == '-')
30572                            && !s.is_empty() =>
30573                    {
30574                        self.write(s);
30575                    }
30576                    _ => {
30577                        self.generate_expression(value)?;
30578                    }
30579                }
30580            }
30581            if let Some(ref unit_spec) = interval.unit {
30582                self.write_space();
30583                self.write_interval_unit_spec(unit_spec)?;
30584            }
30585            Ok(())
30586        } else {
30587            self.generate_expression(expr)
30588        }
30589    }
30590
30591    fn generate_partition_by_truncate(&mut self, e: &PartitionByTruncate) -> Result<()> {
30592        // TRUNCATE(expression, this)
30593        self.write_keyword("TRUNCATE");
30594        self.write("(");
30595        self.generate_expression(&e.expression)?;
30596        self.write(", ");
30597        self.generate_expression(&e.this)?;
30598        self.write(")");
30599        Ok(())
30600    }
30601
30602    fn generate_partition_list(&mut self, e: &PartitionList) -> Result<()> {
30603        // Doris: PARTITION name VALUES IN (val1, val2)
30604        self.write_keyword("PARTITION");
30605        self.write_space();
30606        self.generate_expression(&e.this)?;
30607        self.write_space();
30608        self.write_keyword("VALUES IN");
30609        self.write(" (");
30610        for (i, expr) in e.expressions.iter().enumerate() {
30611            if i > 0 {
30612                self.write(", ");
30613            }
30614            self.generate_expression(expr)?;
30615        }
30616        self.write(")");
30617        Ok(())
30618    }
30619
30620    fn generate_partition_range(&mut self, e: &PartitionRange) -> Result<()> {
30621        // Check if this is a TSQL-style simple range (e.g., "2 TO 5")
30622        // TSQL ranges have no expressions and just use `this TO expression`
30623        if e.expressions.is_empty() && e.expression.is_some() {
30624            // TSQL: simple range like "2 TO 5" - no PARTITION keyword
30625            self.generate_expression(&e.this)?;
30626            self.write_space();
30627            self.write_keyword("TO");
30628            self.write_space();
30629            self.generate_expression(e.expression.as_ref().unwrap())?;
30630            return Ok(());
30631        }
30632
30633        // Doris: PARTITION name VALUES LESS THAN (val) or PARTITION name VALUES [(val1), (val2))
30634        self.write_keyword("PARTITION");
30635        self.write_space();
30636        self.generate_expression(&e.this)?;
30637        self.write_space();
30638
30639        // Check if expressions contain Tuple (bracket notation) or single values (LESS THAN)
30640        if e.expressions.len() == 1 {
30641            // Single value: VALUES LESS THAN (val)
30642            self.write_keyword("VALUES LESS THAN");
30643            self.write(" (");
30644            self.generate_expression(&e.expressions[0])?;
30645            self.write(")");
30646        } else if !e.expressions.is_empty() {
30647            // Multiple values with Tuple: VALUES [(val1), (val2))
30648            self.write_keyword("VALUES");
30649            self.write(" [");
30650            for (i, expr) in e.expressions.iter().enumerate() {
30651                if i > 0 {
30652                    self.write(", ");
30653                }
30654                // If the expr is a Tuple, generate its contents wrapped in parens
30655                if let Expression::Tuple(t) = expr {
30656                    self.write("(");
30657                    for (j, inner) in t.expressions.iter().enumerate() {
30658                        if j > 0 {
30659                            self.write(", ");
30660                        }
30661                        self.generate_expression(inner)?;
30662                    }
30663                    self.write(")");
30664                } else {
30665                    self.write("(");
30666                    self.generate_expression(expr)?;
30667                    self.write(")");
30668                }
30669            }
30670            self.write(")");
30671        }
30672        Ok(())
30673    }
30674
30675    fn generate_partitioned_by_bucket(&mut self, e: &PartitionedByBucket) -> Result<()> {
30676        // BUCKET(this, expression)
30677        self.write_keyword("BUCKET");
30678        self.write("(");
30679        self.generate_expression(&e.this)?;
30680        self.write(", ");
30681        self.generate_expression(&e.expression)?;
30682        self.write(")");
30683        Ok(())
30684    }
30685
30686    fn generate_partitioned_by_property(&mut self, e: &PartitionedByProperty) -> Result<()> {
30687        // PARTITIONED BY this (Teradata/ClickHouse use PARTITION BY)
30688        if matches!(
30689            self.config.dialect,
30690            Some(crate::dialects::DialectType::Teradata)
30691                | Some(crate::dialects::DialectType::ClickHouse)
30692        ) {
30693            self.write_keyword("PARTITION BY");
30694        } else {
30695            self.write_keyword("PARTITIONED BY");
30696        }
30697        self.write_space();
30698        // In pretty mode, always use multiline tuple format for PARTITIONED BY
30699        if self.config.pretty {
30700            if let Expression::Tuple(ref tuple) = *e.this {
30701                self.write("(");
30702                self.write_newline();
30703                self.indent_level += 1;
30704                for (i, expr) in tuple.expressions.iter().enumerate() {
30705                    if i > 0 {
30706                        self.write(",");
30707                        self.write_newline();
30708                    }
30709                    self.write_indent();
30710                    self.generate_expression(expr)?;
30711                }
30712                self.indent_level -= 1;
30713                self.write_newline();
30714                self.write(")");
30715            } else {
30716                self.generate_expression(&e.this)?;
30717            }
30718        } else {
30719            self.generate_expression(&e.this)?;
30720        }
30721        Ok(())
30722    }
30723
30724    fn generate_partitioned_of_property(&mut self, e: &PartitionedOfProperty) -> Result<()> {
30725        // PARTITION OF this FOR VALUES expression or PARTITION OF this DEFAULT
30726        self.write_keyword("PARTITION OF");
30727        self.write_space();
30728        self.generate_expression(&e.this)?;
30729        // Check if expression is a PartitionBoundSpec
30730        if let Expression::PartitionBoundSpec(_) = e.expression.as_ref() {
30731            self.write_space();
30732            self.write_keyword("FOR VALUES");
30733            self.write_space();
30734            self.generate_expression(&e.expression)?;
30735        } else {
30736            self.write_space();
30737            self.write_keyword("DEFAULT");
30738        }
30739        Ok(())
30740    }
30741
30742    fn generate_period_for_system_time_constraint(
30743        &mut self,
30744        e: &PeriodForSystemTimeConstraint,
30745    ) -> Result<()> {
30746        // PERIOD FOR SYSTEM_TIME (this, expression)
30747        self.write_keyword("PERIOD FOR SYSTEM_TIME");
30748        self.write(" (");
30749        self.generate_expression(&e.this)?;
30750        self.write(", ");
30751        self.generate_expression(&e.expression)?;
30752        self.write(")");
30753        Ok(())
30754    }
30755
30756    fn generate_pivot_alias(&mut self, e: &PivotAlias) -> Result<()> {
30757        // value AS alias
30758        // The alias can be an identifier or an expression (e.g., string concatenation)
30759        self.generate_expression(&e.this)?;
30760        self.write_space();
30761        self.write_keyword("AS");
30762        self.write_space();
30763        // When target dialect uses identifiers for UNPIVOT aliases, convert literals to identifiers
30764        if self.config.unpivot_aliases_are_identifiers {
30765            match &e.alias {
30766                Expression::Literal(Literal::String(s)) => {
30767                    // Convert string literal to identifier
30768                    self.generate_identifier(&Identifier::new(s.clone()))?;
30769                }
30770                Expression::Literal(Literal::Number(n)) => {
30771                    // Convert number literal to quoted identifier
30772                    let mut id = Identifier::new(n.clone());
30773                    id.quoted = true;
30774                    self.generate_identifier(&id)?;
30775                }
30776                other => {
30777                    self.generate_expression(other)?;
30778                }
30779            }
30780        } else {
30781            self.generate_expression(&e.alias)?;
30782        }
30783        Ok(())
30784    }
30785
30786    fn generate_pivot_any(&mut self, e: &PivotAny) -> Result<()> {
30787        // ANY or ANY [expression]
30788        self.write_keyword("ANY");
30789        if let Some(this) = &e.this {
30790            self.write_space();
30791            self.generate_expression(this)?;
30792        }
30793        Ok(())
30794    }
30795
30796    fn generate_predict(&mut self, e: &Predict) -> Result<()> {
30797        // ML.PREDICT(MODEL this, expression, [params_struct])
30798        self.write_keyword("ML.PREDICT");
30799        self.write("(");
30800        self.write_keyword("MODEL");
30801        self.write_space();
30802        self.generate_expression(&e.this)?;
30803        self.write(", ");
30804        self.generate_expression(&e.expression)?;
30805        if let Some(params) = &e.params_struct {
30806            self.write(", ");
30807            self.generate_expression(params)?;
30808        }
30809        self.write(")");
30810        Ok(())
30811    }
30812
30813    fn generate_previous_day(&mut self, e: &PreviousDay) -> Result<()> {
30814        // PREVIOUS_DAY(this, expression)
30815        self.write_keyword("PREVIOUS_DAY");
30816        self.write("(");
30817        self.generate_expression(&e.this)?;
30818        self.write(", ");
30819        self.generate_expression(&e.expression)?;
30820        self.write(")");
30821        Ok(())
30822    }
30823
30824    fn generate_primary_key(&mut self, e: &PrimaryKey) -> Result<()> {
30825        // PRIMARY KEY [name] (columns) [INCLUDE (...)] [options]
30826        self.write_keyword("PRIMARY KEY");
30827        if let Some(name) = &e.this {
30828            self.write_space();
30829            self.generate_expression(name)?;
30830        }
30831        if !e.expressions.is_empty() {
30832            self.write(" (");
30833            for (i, expr) in e.expressions.iter().enumerate() {
30834                if i > 0 {
30835                    self.write(", ");
30836                }
30837                self.generate_expression(expr)?;
30838            }
30839            self.write(")");
30840        }
30841        if let Some(include) = &e.include {
30842            self.write_space();
30843            self.generate_expression(include)?;
30844        }
30845        if !e.options.is_empty() {
30846            self.write_space();
30847            for (i, opt) in e.options.iter().enumerate() {
30848                if i > 0 {
30849                    self.write_space();
30850                }
30851                self.generate_expression(opt)?;
30852            }
30853        }
30854        Ok(())
30855    }
30856
30857    fn generate_primary_key_column_constraint(
30858        &mut self,
30859        _e: &PrimaryKeyColumnConstraint,
30860    ) -> Result<()> {
30861        // PRIMARY KEY constraint at column level
30862        self.write_keyword("PRIMARY KEY");
30863        Ok(())
30864    }
30865
30866    fn generate_path_column_constraint(&mut self, e: &PathColumnConstraint) -> Result<()> {
30867        // PATH 'xpath' constraint for XMLTABLE/JSON_TABLE columns
30868        self.write_keyword("PATH");
30869        self.write_space();
30870        self.generate_expression(&e.this)?;
30871        Ok(())
30872    }
30873
30874    fn generate_projection_def(&mut self, e: &ProjectionDef) -> Result<()> {
30875        // PROJECTION this (expression)
30876        self.write_keyword("PROJECTION");
30877        self.write_space();
30878        self.generate_expression(&e.this)?;
30879        self.write(" (");
30880        self.generate_expression(&e.expression)?;
30881        self.write(")");
30882        Ok(())
30883    }
30884
30885    fn generate_properties(&mut self, e: &Properties) -> Result<()> {
30886        // Properties list
30887        for (i, prop) in e.expressions.iter().enumerate() {
30888            if i > 0 {
30889                self.write(", ");
30890            }
30891            self.generate_expression(prop)?;
30892        }
30893        Ok(())
30894    }
30895
30896    fn generate_property(&mut self, e: &Property) -> Result<()> {
30897        // name=value
30898        self.generate_expression(&e.this)?;
30899        if let Some(value) = &e.value {
30900            self.write("=");
30901            self.generate_expression(value)?;
30902        }
30903        Ok(())
30904    }
30905
30906    /// Generate BigQuery-style OPTIONS clause: OPTIONS (key=value, key=value, ...)
30907    fn generate_options_clause(&mut self, options: &[Expression]) -> Result<()> {
30908        self.write_keyword("OPTIONS");
30909        self.write(" (");
30910        for (i, opt) in options.iter().enumerate() {
30911            if i > 0 {
30912                self.write(", ");
30913            }
30914            self.generate_option_expression(opt)?;
30915        }
30916        self.write(")");
30917        Ok(())
30918    }
30919
30920    /// Generate Doris/StarRocks-style PROPERTIES clause: PROPERTIES ('key'='value', 'key'='value', ...)
30921    fn generate_properties_clause(&mut self, properties: &[Expression]) -> Result<()> {
30922        self.write_keyword("PROPERTIES");
30923        self.write(" (");
30924        for (i, prop) in properties.iter().enumerate() {
30925            if i > 0 {
30926                self.write(", ");
30927            }
30928            self.generate_option_expression(prop)?;
30929        }
30930        self.write(")");
30931        Ok(())
30932    }
30933
30934    /// Generate Databricks-style ENVIRONMENT clause: ENVIRONMENT (key = 'value', key = 'value', ...)
30935    fn generate_environment_clause(&mut self, environment: &[Expression]) -> Result<()> {
30936        self.write_keyword("ENVIRONMENT");
30937        self.write(" (");
30938        for (i, env_item) in environment.iter().enumerate() {
30939            if i > 0 {
30940                self.write(", ");
30941            }
30942            self.generate_environment_expression(env_item)?;
30943        }
30944        self.write(")");
30945        Ok(())
30946    }
30947
30948    /// Generate an environment expression with spaces around =
30949    fn generate_environment_expression(&mut self, expr: &Expression) -> Result<()> {
30950        match expr {
30951            Expression::Eq(eq) => {
30952                // Generate key = value with spaces (Databricks ENVIRONMENT style)
30953                self.generate_expression(&eq.left)?;
30954                self.write(" = ");
30955                self.generate_expression(&eq.right)?;
30956                Ok(())
30957            }
30958            _ => self.generate_expression(expr),
30959        }
30960    }
30961
30962    /// Generate Hive-style TBLPROPERTIES clause: TBLPROPERTIES ('key'='value', ...)
30963    fn generate_tblproperties_clause(&mut self, options: &[Expression]) -> Result<()> {
30964        self.write_keyword("TBLPROPERTIES");
30965        if self.config.pretty {
30966            self.write(" (");
30967            self.write_newline();
30968            self.indent_level += 1;
30969            for (i, opt) in options.iter().enumerate() {
30970                if i > 0 {
30971                    self.write(",");
30972                    self.write_newline();
30973                }
30974                self.write_indent();
30975                self.generate_option_expression(opt)?;
30976            }
30977            self.indent_level -= 1;
30978            self.write_newline();
30979            self.write(")");
30980        } else {
30981            self.write(" (");
30982            for (i, opt) in options.iter().enumerate() {
30983                if i > 0 {
30984                    self.write(", ");
30985                }
30986                self.generate_option_expression(opt)?;
30987            }
30988            self.write(")");
30989        }
30990        Ok(())
30991    }
30992
30993    /// Generate an option expression without spaces around =
30994    fn generate_option_expression(&mut self, expr: &Expression) -> Result<()> {
30995        match expr {
30996            Expression::Eq(eq) => {
30997                // Generate key=value without spaces
30998                self.generate_expression(&eq.left)?;
30999                self.write("=");
31000                self.generate_expression(&eq.right)?;
31001                Ok(())
31002            }
31003            _ => self.generate_expression(expr),
31004        }
31005    }
31006
31007    fn generate_pseudo_type(&mut self, e: &PseudoType) -> Result<()> {
31008        // Just output the name
31009        self.generate_expression(&e.this)?;
31010        Ok(())
31011    }
31012
31013    fn generate_put(&mut self, e: &PutStmt) -> Result<()> {
31014        // PUT source_file @stage [options]
31015        self.write_keyword("PUT");
31016        self.write_space();
31017
31018        // Source file path - preserve original quoting
31019        if e.source_quoted {
31020            self.write("'");
31021            self.write(&e.source);
31022            self.write("'");
31023        } else {
31024            self.write(&e.source);
31025        }
31026
31027        self.write_space();
31028
31029        // Target stage reference - output the string directly (includes @)
31030        if let Expression::Literal(Literal::String(s)) = &e.target {
31031            self.write(s);
31032        } else {
31033            self.generate_expression(&e.target)?;
31034        }
31035
31036        // Optional parameters: KEY=VALUE
31037        for param in &e.params {
31038            self.write_space();
31039            self.write(&param.name);
31040            if let Some(ref value) = param.value {
31041                self.write("=");
31042                self.generate_expression(value)?;
31043            }
31044        }
31045
31046        Ok(())
31047    }
31048
31049    fn generate_quantile(&mut self, e: &Quantile) -> Result<()> {
31050        // QUANTILE(this, quantile)
31051        self.write_keyword("QUANTILE");
31052        self.write("(");
31053        self.generate_expression(&e.this)?;
31054        if let Some(quantile) = &e.quantile {
31055            self.write(", ");
31056            self.generate_expression(quantile)?;
31057        }
31058        self.write(")");
31059        Ok(())
31060    }
31061
31062    fn generate_query_band(&mut self, e: &QueryBand) -> Result<()> {
31063        // QUERY_BAND = this [UPDATE] [FOR scope]
31064        if matches!(
31065            self.config.dialect,
31066            Some(crate::dialects::DialectType::Teradata)
31067        ) {
31068            self.write_keyword("SET");
31069            self.write_space();
31070        }
31071        self.write_keyword("QUERY_BAND");
31072        self.write(" = ");
31073        self.generate_expression(&e.this)?;
31074        if e.update.is_some() {
31075            self.write_space();
31076            self.write_keyword("UPDATE");
31077        }
31078        if let Some(scope) = &e.scope {
31079            self.write_space();
31080            self.write_keyword("FOR");
31081            self.write_space();
31082            self.generate_expression(scope)?;
31083        }
31084        Ok(())
31085    }
31086
31087    fn generate_query_option(&mut self, e: &QueryOption) -> Result<()> {
31088        // this = expression
31089        self.generate_expression(&e.this)?;
31090        if let Some(expression) = &e.expression {
31091            self.write(" = ");
31092            self.generate_expression(expression)?;
31093        }
31094        Ok(())
31095    }
31096
31097    fn generate_query_transform(&mut self, e: &QueryTransform) -> Result<()> {
31098        // TRANSFORM (expressions) [row_format_before] [RECORDWRITER record_writer] USING command_script [AS schema] [row_format_after] [RECORDREADER record_reader]
31099        self.write_keyword("TRANSFORM");
31100        self.write("(");
31101        for (i, expr) in e.expressions.iter().enumerate() {
31102            if i > 0 {
31103                self.write(", ");
31104            }
31105            self.generate_expression(expr)?;
31106        }
31107        self.write(")");
31108        if let Some(row_format_before) = &e.row_format_before {
31109            self.write_space();
31110            self.generate_expression(row_format_before)?;
31111        }
31112        if let Some(record_writer) = &e.record_writer {
31113            self.write_space();
31114            self.write_keyword("RECORDWRITER");
31115            self.write_space();
31116            self.generate_expression(record_writer)?;
31117        }
31118        if let Some(command_script) = &e.command_script {
31119            self.write_space();
31120            self.write_keyword("USING");
31121            self.write_space();
31122            self.generate_expression(command_script)?;
31123        }
31124        if let Some(schema) = &e.schema {
31125            self.write_space();
31126            self.write_keyword("AS");
31127            self.write_space();
31128            self.generate_expression(schema)?;
31129        }
31130        if let Some(row_format_after) = &e.row_format_after {
31131            self.write_space();
31132            self.generate_expression(row_format_after)?;
31133        }
31134        if let Some(record_reader) = &e.record_reader {
31135            self.write_space();
31136            self.write_keyword("RECORDREADER");
31137            self.write_space();
31138            self.generate_expression(record_reader)?;
31139        }
31140        Ok(())
31141    }
31142
31143    fn generate_randn(&mut self, e: &Randn) -> Result<()> {
31144        // RANDN([seed])
31145        self.write_keyword("RANDN");
31146        self.write("(");
31147        if let Some(this) = &e.this {
31148            self.generate_expression(this)?;
31149        }
31150        self.write(")");
31151        Ok(())
31152    }
31153
31154    fn generate_randstr(&mut self, e: &Randstr) -> Result<()> {
31155        // RANDSTR(this, [generator])
31156        self.write_keyword("RANDSTR");
31157        self.write("(");
31158        self.generate_expression(&e.this)?;
31159        if let Some(generator) = &e.generator {
31160            self.write(", ");
31161            self.generate_expression(generator)?;
31162        }
31163        self.write(")");
31164        Ok(())
31165    }
31166
31167    fn generate_range_bucket(&mut self, e: &RangeBucket) -> Result<()> {
31168        // RANGE_BUCKET(this, expression)
31169        self.write_keyword("RANGE_BUCKET");
31170        self.write("(");
31171        self.generate_expression(&e.this)?;
31172        self.write(", ");
31173        self.generate_expression(&e.expression)?;
31174        self.write(")");
31175        Ok(())
31176    }
31177
31178    fn generate_range_n(&mut self, e: &RangeN) -> Result<()> {
31179        // RANGE_N(this BETWEEN expressions [EACH each])
31180        self.write_keyword("RANGE_N");
31181        self.write("(");
31182        self.generate_expression(&e.this)?;
31183        self.write_space();
31184        self.write_keyword("BETWEEN");
31185        self.write_space();
31186        for (i, expr) in e.expressions.iter().enumerate() {
31187            if i > 0 {
31188                self.write(", ");
31189            }
31190            self.generate_expression(expr)?;
31191        }
31192        if let Some(each) = &e.each {
31193            self.write_space();
31194            self.write_keyword("EACH");
31195            self.write_space();
31196            self.generate_expression(each)?;
31197        }
31198        self.write(")");
31199        Ok(())
31200    }
31201
31202    fn generate_read_csv(&mut self, e: &ReadCSV) -> Result<()> {
31203        // READ_CSV(this, expressions...)
31204        self.write_keyword("READ_CSV");
31205        self.write("(");
31206        self.generate_expression(&e.this)?;
31207        for expr in &e.expressions {
31208            self.write(", ");
31209            self.generate_expression(expr)?;
31210        }
31211        self.write(")");
31212        Ok(())
31213    }
31214
31215    fn generate_read_parquet(&mut self, e: &ReadParquet) -> Result<()> {
31216        // READ_PARQUET(expressions...)
31217        self.write_keyword("READ_PARQUET");
31218        self.write("(");
31219        for (i, expr) in e.expressions.iter().enumerate() {
31220            if i > 0 {
31221                self.write(", ");
31222            }
31223            self.generate_expression(expr)?;
31224        }
31225        self.write(")");
31226        Ok(())
31227    }
31228
31229    fn generate_recursive_with_search(&mut self, e: &RecursiveWithSearch) -> Result<()> {
31230        // SEARCH kind FIRST BY this SET expression [USING using]
31231        // or CYCLE this SET expression [USING using]
31232        if e.kind == "CYCLE" {
31233            self.write_keyword("CYCLE");
31234        } else {
31235            self.write_keyword("SEARCH");
31236            self.write_space();
31237            self.write(&e.kind);
31238            self.write_space();
31239            self.write_keyword("FIRST BY");
31240        }
31241        self.write_space();
31242        self.generate_expression(&e.this)?;
31243        self.write_space();
31244        self.write_keyword("SET");
31245        self.write_space();
31246        self.generate_expression(&e.expression)?;
31247        if let Some(using) = &e.using {
31248            self.write_space();
31249            self.write_keyword("USING");
31250            self.write_space();
31251            self.generate_expression(using)?;
31252        }
31253        Ok(())
31254    }
31255
31256    fn generate_reduce(&mut self, e: &Reduce) -> Result<()> {
31257        // REDUCE(this, initial, merge, [finish])
31258        self.write_keyword("REDUCE");
31259        self.write("(");
31260        self.generate_expression(&e.this)?;
31261        if let Some(initial) = &e.initial {
31262            self.write(", ");
31263            self.generate_expression(initial)?;
31264        }
31265        if let Some(merge) = &e.merge {
31266            self.write(", ");
31267            self.generate_expression(merge)?;
31268        }
31269        if let Some(finish) = &e.finish {
31270            self.write(", ");
31271            self.generate_expression(finish)?;
31272        }
31273        self.write(")");
31274        Ok(())
31275    }
31276
31277    fn generate_reference(&mut self, e: &Reference) -> Result<()> {
31278        // REFERENCES this (expressions) [options]
31279        self.write_keyword("REFERENCES");
31280        self.write_space();
31281        self.generate_expression(&e.this)?;
31282        if !e.expressions.is_empty() {
31283            self.write(" (");
31284            for (i, expr) in e.expressions.iter().enumerate() {
31285                if i > 0 {
31286                    self.write(", ");
31287                }
31288                self.generate_expression(expr)?;
31289            }
31290            self.write(")");
31291        }
31292        for opt in &e.options {
31293            self.write_space();
31294            self.generate_expression(opt)?;
31295        }
31296        Ok(())
31297    }
31298
31299    fn generate_refresh(&mut self, e: &Refresh) -> Result<()> {
31300        // REFRESH [kind] this
31301        self.write_keyword("REFRESH");
31302        if !e.kind.is_empty() {
31303            self.write_space();
31304            self.write_keyword(&e.kind);
31305        }
31306        self.write_space();
31307        self.generate_expression(&e.this)?;
31308        Ok(())
31309    }
31310
31311    fn generate_refresh_trigger_property(&mut self, e: &RefreshTriggerProperty) -> Result<()> {
31312        // Doris REFRESH clause: REFRESH method ON kind [EVERY n UNIT] [STARTS 'datetime']
31313        self.write_keyword("REFRESH");
31314        self.write_space();
31315        self.write_keyword(&e.method);
31316
31317        if let Some(ref kind) = e.kind {
31318            self.write_space();
31319            self.write_keyword("ON");
31320            self.write_space();
31321            self.write_keyword(kind);
31322
31323            // EVERY n UNIT
31324            if let Some(ref every) = e.every {
31325                self.write_space();
31326                self.write_keyword("EVERY");
31327                self.write_space();
31328                self.generate_expression(every)?;
31329                if let Some(ref unit) = e.unit {
31330                    self.write_space();
31331                    self.write_keyword(unit);
31332                }
31333            }
31334
31335            // STARTS 'datetime'
31336            if let Some(ref starts) = e.starts {
31337                self.write_space();
31338                self.write_keyword("STARTS");
31339                self.write_space();
31340                self.generate_expression(starts)?;
31341            }
31342        }
31343        Ok(())
31344    }
31345
31346    fn generate_regexp_count(&mut self, e: &RegexpCount) -> Result<()> {
31347        // REGEXP_COUNT(this, expression, position, parameters)
31348        self.write_keyword("REGEXP_COUNT");
31349        self.write("(");
31350        self.generate_expression(&e.this)?;
31351        self.write(", ");
31352        self.generate_expression(&e.expression)?;
31353        if let Some(position) = &e.position {
31354            self.write(", ");
31355            self.generate_expression(position)?;
31356        }
31357        if let Some(parameters) = &e.parameters {
31358            self.write(", ");
31359            self.generate_expression(parameters)?;
31360        }
31361        self.write(")");
31362        Ok(())
31363    }
31364
31365    fn generate_regexp_extract_all(&mut self, e: &RegexpExtractAll) -> Result<()> {
31366        // REGEXP_EXTRACT_ALL(this, expression, group, parameters, position, occurrence)
31367        self.write_keyword("REGEXP_EXTRACT_ALL");
31368        self.write("(");
31369        self.generate_expression(&e.this)?;
31370        self.write(", ");
31371        self.generate_expression(&e.expression)?;
31372        if let Some(group) = &e.group {
31373            self.write(", ");
31374            self.generate_expression(group)?;
31375        }
31376        self.write(")");
31377        Ok(())
31378    }
31379
31380    fn generate_regexp_full_match(&mut self, e: &RegexpFullMatch) -> Result<()> {
31381        // REGEXP_FULL_MATCH(this, expression)
31382        self.write_keyword("REGEXP_FULL_MATCH");
31383        self.write("(");
31384        self.generate_expression(&e.this)?;
31385        self.write(", ");
31386        self.generate_expression(&e.expression)?;
31387        self.write(")");
31388        Ok(())
31389    }
31390
31391    fn generate_regexp_i_like(&mut self, e: &RegexpILike) -> Result<()> {
31392        use crate::dialects::DialectType;
31393        // PostgreSQL/Redshift uses ~* operator for case-insensitive regex matching
31394        if matches!(
31395            self.config.dialect,
31396            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
31397        ) && e.flag.is_none()
31398        {
31399            self.generate_expression(&e.this)?;
31400            self.write(" ~* ");
31401            self.generate_expression(&e.expression)?;
31402        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
31403            // Snowflake uses REGEXP_LIKE(x, pattern, 'i')
31404            self.write_keyword("REGEXP_LIKE");
31405            self.write("(");
31406            self.generate_expression(&e.this)?;
31407            self.write(", ");
31408            self.generate_expression(&e.expression)?;
31409            self.write(", ");
31410            if let Some(flag) = &e.flag {
31411                self.generate_expression(flag)?;
31412            } else {
31413                self.write("'i'");
31414            }
31415            self.write(")");
31416        } else {
31417            // this REGEXP_ILIKE expression or REGEXP_ILIKE(this, expression, flag)
31418            self.generate_expression(&e.this)?;
31419            self.write_space();
31420            self.write_keyword("REGEXP_ILIKE");
31421            self.write_space();
31422            self.generate_expression(&e.expression)?;
31423            if let Some(flag) = &e.flag {
31424                self.write(", ");
31425                self.generate_expression(flag)?;
31426            }
31427        }
31428        Ok(())
31429    }
31430
31431    fn generate_regexp_instr(&mut self, e: &RegexpInstr) -> Result<()> {
31432        // REGEXP_INSTR(this, expression, position, occurrence, option, parameters, group)
31433        self.write_keyword("REGEXP_INSTR");
31434        self.write("(");
31435        self.generate_expression(&e.this)?;
31436        self.write(", ");
31437        self.generate_expression(&e.expression)?;
31438        if let Some(position) = &e.position {
31439            self.write(", ");
31440            self.generate_expression(position)?;
31441        }
31442        if let Some(occurrence) = &e.occurrence {
31443            self.write(", ");
31444            self.generate_expression(occurrence)?;
31445        }
31446        if let Some(option) = &e.option {
31447            self.write(", ");
31448            self.generate_expression(option)?;
31449        }
31450        if let Some(parameters) = &e.parameters {
31451            self.write(", ");
31452            self.generate_expression(parameters)?;
31453        }
31454        if let Some(group) = &e.group {
31455            self.write(", ");
31456            self.generate_expression(group)?;
31457        }
31458        self.write(")");
31459        Ok(())
31460    }
31461
31462    fn generate_regexp_split(&mut self, e: &RegexpSplit) -> Result<()> {
31463        // REGEXP_SPLIT(this, expression, limit)
31464        self.write_keyword("REGEXP_SPLIT");
31465        self.write("(");
31466        self.generate_expression(&e.this)?;
31467        self.write(", ");
31468        self.generate_expression(&e.expression)?;
31469        if let Some(limit) = &e.limit {
31470            self.write(", ");
31471            self.generate_expression(limit)?;
31472        }
31473        self.write(")");
31474        Ok(())
31475    }
31476
31477    fn generate_regr_avgx(&mut self, e: &RegrAvgx) -> Result<()> {
31478        // REGR_AVGX(this, expression)
31479        self.write_keyword("REGR_AVGX");
31480        self.write("(");
31481        self.generate_expression(&e.this)?;
31482        self.write(", ");
31483        self.generate_expression(&e.expression)?;
31484        self.write(")");
31485        Ok(())
31486    }
31487
31488    fn generate_regr_avgy(&mut self, e: &RegrAvgy) -> Result<()> {
31489        // REGR_AVGY(this, expression)
31490        self.write_keyword("REGR_AVGY");
31491        self.write("(");
31492        self.generate_expression(&e.this)?;
31493        self.write(", ");
31494        self.generate_expression(&e.expression)?;
31495        self.write(")");
31496        Ok(())
31497    }
31498
31499    fn generate_regr_count(&mut self, e: &RegrCount) -> Result<()> {
31500        // REGR_COUNT(this, expression)
31501        self.write_keyword("REGR_COUNT");
31502        self.write("(");
31503        self.generate_expression(&e.this)?;
31504        self.write(", ");
31505        self.generate_expression(&e.expression)?;
31506        self.write(")");
31507        Ok(())
31508    }
31509
31510    fn generate_regr_intercept(&mut self, e: &RegrIntercept) -> Result<()> {
31511        // REGR_INTERCEPT(this, expression)
31512        self.write_keyword("REGR_INTERCEPT");
31513        self.write("(");
31514        self.generate_expression(&e.this)?;
31515        self.write(", ");
31516        self.generate_expression(&e.expression)?;
31517        self.write(")");
31518        Ok(())
31519    }
31520
31521    fn generate_regr_r2(&mut self, e: &RegrR2) -> Result<()> {
31522        // REGR_R2(this, expression)
31523        self.write_keyword("REGR_R2");
31524        self.write("(");
31525        self.generate_expression(&e.this)?;
31526        self.write(", ");
31527        self.generate_expression(&e.expression)?;
31528        self.write(")");
31529        Ok(())
31530    }
31531
31532    fn generate_regr_slope(&mut self, e: &RegrSlope) -> Result<()> {
31533        // REGR_SLOPE(this, expression)
31534        self.write_keyword("REGR_SLOPE");
31535        self.write("(");
31536        self.generate_expression(&e.this)?;
31537        self.write(", ");
31538        self.generate_expression(&e.expression)?;
31539        self.write(")");
31540        Ok(())
31541    }
31542
31543    fn generate_regr_sxx(&mut self, e: &RegrSxx) -> Result<()> {
31544        // REGR_SXX(this, expression)
31545        self.write_keyword("REGR_SXX");
31546        self.write("(");
31547        self.generate_expression(&e.this)?;
31548        self.write(", ");
31549        self.generate_expression(&e.expression)?;
31550        self.write(")");
31551        Ok(())
31552    }
31553
31554    fn generate_regr_sxy(&mut self, e: &RegrSxy) -> Result<()> {
31555        // REGR_SXY(this, expression)
31556        self.write_keyword("REGR_SXY");
31557        self.write("(");
31558        self.generate_expression(&e.this)?;
31559        self.write(", ");
31560        self.generate_expression(&e.expression)?;
31561        self.write(")");
31562        Ok(())
31563    }
31564
31565    fn generate_regr_syy(&mut self, e: &RegrSyy) -> Result<()> {
31566        // REGR_SYY(this, expression)
31567        self.write_keyword("REGR_SYY");
31568        self.write("(");
31569        self.generate_expression(&e.this)?;
31570        self.write(", ");
31571        self.generate_expression(&e.expression)?;
31572        self.write(")");
31573        Ok(())
31574    }
31575
31576    fn generate_regr_valx(&mut self, e: &RegrValx) -> Result<()> {
31577        // REGR_VALX(this, expression)
31578        self.write_keyword("REGR_VALX");
31579        self.write("(");
31580        self.generate_expression(&e.this)?;
31581        self.write(", ");
31582        self.generate_expression(&e.expression)?;
31583        self.write(")");
31584        Ok(())
31585    }
31586
31587    fn generate_regr_valy(&mut self, e: &RegrValy) -> Result<()> {
31588        // REGR_VALY(this, expression)
31589        self.write_keyword("REGR_VALY");
31590        self.write("(");
31591        self.generate_expression(&e.this)?;
31592        self.write(", ");
31593        self.generate_expression(&e.expression)?;
31594        self.write(")");
31595        Ok(())
31596    }
31597
31598    fn generate_remote_with_connection_model_property(
31599        &mut self,
31600        e: &RemoteWithConnectionModelProperty,
31601    ) -> Result<()> {
31602        // REMOTE WITH CONNECTION this
31603        self.write_keyword("REMOTE WITH CONNECTION");
31604        self.write_space();
31605        self.generate_expression(&e.this)?;
31606        Ok(())
31607    }
31608
31609    fn generate_rename_column(&mut self, e: &RenameColumn) -> Result<()> {
31610        // RENAME COLUMN [IF EXISTS] this TO new_name
31611        self.write_keyword("RENAME COLUMN");
31612        if e.exists {
31613            self.write_space();
31614            self.write_keyword("IF EXISTS");
31615        }
31616        self.write_space();
31617        self.generate_expression(&e.this)?;
31618        if let Some(to) = &e.to {
31619            self.write_space();
31620            self.write_keyword("TO");
31621            self.write_space();
31622            self.generate_expression(to)?;
31623        }
31624        Ok(())
31625    }
31626
31627    fn generate_replace_partition(&mut self, e: &ReplacePartition) -> Result<()> {
31628        // REPLACE PARTITION expression [FROM source]
31629        self.write_keyword("REPLACE PARTITION");
31630        self.write_space();
31631        self.generate_expression(&e.expression)?;
31632        if let Some(source) = &e.source {
31633            self.write_space();
31634            self.write_keyword("FROM");
31635            self.write_space();
31636            self.generate_expression(source)?;
31637        }
31638        Ok(())
31639    }
31640
31641    fn generate_returning(&mut self, e: &Returning) -> Result<()> {
31642        // RETURNING expressions [INTO into]
31643        // TSQL and Fabric use OUTPUT instead of RETURNING
31644        let keyword = match self.config.dialect {
31645            Some(DialectType::TSQL) | Some(DialectType::Fabric) => "OUTPUT",
31646            _ => "RETURNING",
31647        };
31648        self.write_keyword(keyword);
31649        self.write_space();
31650        for (i, expr) in e.expressions.iter().enumerate() {
31651            if i > 0 {
31652                self.write(", ");
31653            }
31654            self.generate_expression(expr)?;
31655        }
31656        if let Some(into) = &e.into {
31657            self.write_space();
31658            self.write_keyword("INTO");
31659            self.write_space();
31660            self.generate_expression(into)?;
31661        }
31662        Ok(())
31663    }
31664
31665    fn generate_output_clause(&mut self, output: &OutputClause) -> Result<()> {
31666        // OUTPUT expressions [INTO into_table]
31667        self.write_space();
31668        self.write_keyword("OUTPUT");
31669        self.write_space();
31670        for (i, expr) in output.columns.iter().enumerate() {
31671            if i > 0 {
31672                self.write(", ");
31673            }
31674            self.generate_expression(expr)?;
31675        }
31676        if let Some(into_table) = &output.into_table {
31677            self.write_space();
31678            self.write_keyword("INTO");
31679            self.write_space();
31680            self.generate_expression(into_table)?;
31681        }
31682        Ok(())
31683    }
31684
31685    fn generate_returns_property(&mut self, e: &ReturnsProperty) -> Result<()> {
31686        // RETURNS [TABLE] this [NULL ON NULL INPUT | CALLED ON NULL INPUT]
31687        self.write_keyword("RETURNS");
31688        if e.is_table.is_some() {
31689            self.write_space();
31690            self.write_keyword("TABLE");
31691        }
31692        if let Some(table) = &e.table {
31693            self.write_space();
31694            self.generate_expression(table)?;
31695        } else if let Some(this) = &e.this {
31696            self.write_space();
31697            self.generate_expression(this)?;
31698        }
31699        if e.null.is_some() {
31700            self.write_space();
31701            self.write_keyword("NULL ON NULL INPUT");
31702        }
31703        Ok(())
31704    }
31705
31706    fn generate_rollback(&mut self, e: &Rollback) -> Result<()> {
31707        // ROLLBACK [TRANSACTION [transaction_name]] [TO savepoint]
31708        self.write_keyword("ROLLBACK");
31709
31710        // TSQL always uses ROLLBACK TRANSACTION
31711        if e.this.is_none()
31712            && matches!(
31713                self.config.dialect,
31714                Some(DialectType::TSQL) | Some(DialectType::Fabric)
31715            )
31716        {
31717            self.write_space();
31718            self.write_keyword("TRANSACTION");
31719        }
31720
31721        // Check if this has TRANSACTION keyword or transaction name
31722        if let Some(this) = &e.this {
31723            // Check if it's just the "TRANSACTION" marker or an actual transaction name
31724            let is_transaction_marker = matches!(
31725                this.as_ref(),
31726                Expression::Identifier(id) if id.name == "TRANSACTION"
31727            );
31728
31729            self.write_space();
31730            self.write_keyword("TRANSACTION");
31731
31732            // If it's a real transaction name, output it
31733            if !is_transaction_marker {
31734                self.write_space();
31735                self.generate_expression(this)?;
31736            }
31737        }
31738
31739        // Output TO savepoint
31740        if let Some(savepoint) = &e.savepoint {
31741            self.write_space();
31742            self.write_keyword("TO");
31743            self.write_space();
31744            self.generate_expression(savepoint)?;
31745        }
31746        Ok(())
31747    }
31748
31749    fn generate_rollup(&mut self, e: &Rollup) -> Result<()> {
31750        // Python: return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
31751        if e.expressions.is_empty() {
31752            self.write_keyword("WITH ROLLUP");
31753        } else {
31754            self.write_keyword("ROLLUP");
31755            self.write("(");
31756            for (i, expr) in e.expressions.iter().enumerate() {
31757                if i > 0 {
31758                    self.write(", ");
31759                }
31760                self.generate_expression(expr)?;
31761            }
31762            self.write(")");
31763        }
31764        Ok(())
31765    }
31766
31767    fn generate_row_format_delimited_property(
31768        &mut self,
31769        e: &RowFormatDelimitedProperty,
31770    ) -> Result<()> {
31771        // ROW FORMAT DELIMITED [FIELDS TERMINATED BY ...] [ESCAPED BY ...] [COLLECTION ITEMS TERMINATED BY ...] [MAP KEYS TERMINATED BY ...] [LINES TERMINATED BY ...] [NULL DEFINED AS ...]
31772        self.write_keyword("ROW FORMAT DELIMITED");
31773        if let Some(fields) = &e.fields {
31774            self.write_space();
31775            self.write_keyword("FIELDS TERMINATED BY");
31776            self.write_space();
31777            self.generate_expression(fields)?;
31778        }
31779        if let Some(escaped) = &e.escaped {
31780            self.write_space();
31781            self.write_keyword("ESCAPED BY");
31782            self.write_space();
31783            self.generate_expression(escaped)?;
31784        }
31785        if let Some(items) = &e.collection_items {
31786            self.write_space();
31787            self.write_keyword("COLLECTION ITEMS TERMINATED BY");
31788            self.write_space();
31789            self.generate_expression(items)?;
31790        }
31791        if let Some(keys) = &e.map_keys {
31792            self.write_space();
31793            self.write_keyword("MAP KEYS TERMINATED BY");
31794            self.write_space();
31795            self.generate_expression(keys)?;
31796        }
31797        if let Some(lines) = &e.lines {
31798            self.write_space();
31799            self.write_keyword("LINES TERMINATED BY");
31800            self.write_space();
31801            self.generate_expression(lines)?;
31802        }
31803        if let Some(null) = &e.null {
31804            self.write_space();
31805            self.write_keyword("NULL DEFINED AS");
31806            self.write_space();
31807            self.generate_expression(null)?;
31808        }
31809        if let Some(serde) = &e.serde {
31810            self.write_space();
31811            self.generate_expression(serde)?;
31812        }
31813        Ok(())
31814    }
31815
31816    fn generate_row_format_property(&mut self, e: &RowFormatProperty) -> Result<()> {
31817        // ROW FORMAT this
31818        self.write_keyword("ROW FORMAT");
31819        self.write_space();
31820        self.generate_expression(&e.this)?;
31821        Ok(())
31822    }
31823
31824    fn generate_row_format_serde_property(&mut self, e: &RowFormatSerdeProperty) -> Result<()> {
31825        // ROW FORMAT SERDE this [WITH SERDEPROPERTIES (...)]
31826        self.write_keyword("ROW FORMAT SERDE");
31827        self.write_space();
31828        self.generate_expression(&e.this)?;
31829        if let Some(props) = &e.serde_properties {
31830            self.write_space();
31831            // SerdeProperties generates its own "[WITH] SERDEPROPERTIES (...)"
31832            self.generate_expression(props)?;
31833        }
31834        Ok(())
31835    }
31836
31837    fn generate_sha2(&mut self, e: &SHA2) -> Result<()> {
31838        // SHA2(this, length)
31839        self.write_keyword("SHA2");
31840        self.write("(");
31841        self.generate_expression(&e.this)?;
31842        if let Some(length) = e.length {
31843            self.write(", ");
31844            self.write(&length.to_string());
31845        }
31846        self.write(")");
31847        Ok(())
31848    }
31849
31850    fn generate_sha2_digest(&mut self, e: &SHA2Digest) -> Result<()> {
31851        // SHA2_DIGEST(this, length)
31852        self.write_keyword("SHA2_DIGEST");
31853        self.write("(");
31854        self.generate_expression(&e.this)?;
31855        if let Some(length) = e.length {
31856            self.write(", ");
31857            self.write(&length.to_string());
31858        }
31859        self.write(")");
31860        Ok(())
31861    }
31862
31863    fn generate_safe_add(&mut self, e: &SafeAdd) -> Result<()> {
31864        let name = if matches!(
31865            self.config.dialect,
31866            Some(crate::dialects::DialectType::Spark)
31867                | Some(crate::dialects::DialectType::Databricks)
31868        ) {
31869            "TRY_ADD"
31870        } else {
31871            "SAFE_ADD"
31872        };
31873        self.write_keyword(name);
31874        self.write("(");
31875        self.generate_expression(&e.this)?;
31876        self.write(", ");
31877        self.generate_expression(&e.expression)?;
31878        self.write(")");
31879        Ok(())
31880    }
31881
31882    fn generate_safe_divide(&mut self, e: &SafeDivide) -> Result<()> {
31883        // SAFE_DIVIDE(this, expression)
31884        self.write_keyword("SAFE_DIVIDE");
31885        self.write("(");
31886        self.generate_expression(&e.this)?;
31887        self.write(", ");
31888        self.generate_expression(&e.expression)?;
31889        self.write(")");
31890        Ok(())
31891    }
31892
31893    fn generate_safe_multiply(&mut self, e: &SafeMultiply) -> Result<()> {
31894        let name = if matches!(
31895            self.config.dialect,
31896            Some(crate::dialects::DialectType::Spark)
31897                | Some(crate::dialects::DialectType::Databricks)
31898        ) {
31899            "TRY_MULTIPLY"
31900        } else {
31901            "SAFE_MULTIPLY"
31902        };
31903        self.write_keyword(name);
31904        self.write("(");
31905        self.generate_expression(&e.this)?;
31906        self.write(", ");
31907        self.generate_expression(&e.expression)?;
31908        self.write(")");
31909        Ok(())
31910    }
31911
31912    fn generate_safe_subtract(&mut self, e: &SafeSubtract) -> Result<()> {
31913        let name = if matches!(
31914            self.config.dialect,
31915            Some(crate::dialects::DialectType::Spark)
31916                | Some(crate::dialects::DialectType::Databricks)
31917        ) {
31918            "TRY_SUBTRACT"
31919        } else {
31920            "SAFE_SUBTRACT"
31921        };
31922        self.write_keyword(name);
31923        self.write("(");
31924        self.generate_expression(&e.this)?;
31925        self.write(", ");
31926        self.generate_expression(&e.expression)?;
31927        self.write(")");
31928        Ok(())
31929    }
31930
31931    /// Generate the body of a USING SAMPLE or TABLESAMPLE clause:
31932    /// METHOD (size UNIT) [REPEATABLE (seed)]
31933    fn generate_sample_body(&mut self, sample: &Sample) -> Result<()> {
31934        // Handle BUCKET sampling: TABLESAMPLE (BUCKET n OUT OF m [ON col])
31935        if matches!(sample.method, SampleMethod::Bucket) {
31936            self.write(" (");
31937            self.write_keyword("BUCKET");
31938            self.write_space();
31939            if let Some(ref num) = sample.bucket_numerator {
31940                self.generate_expression(num)?;
31941            }
31942            self.write_space();
31943            self.write_keyword("OUT OF");
31944            self.write_space();
31945            if let Some(ref denom) = sample.bucket_denominator {
31946                self.generate_expression(denom)?;
31947            }
31948            if let Some(ref field) = sample.bucket_field {
31949                self.write_space();
31950                self.write_keyword("ON");
31951                self.write_space();
31952                self.generate_expression(field)?;
31953            }
31954            self.write(")");
31955            return Ok(());
31956        }
31957
31958        // Output method name if explicitly specified, or for dialects that always require it
31959        let is_snowflake = matches!(
31960            self.config.dialect,
31961            Some(crate::dialects::DialectType::Snowflake)
31962        );
31963        let is_postgres = matches!(
31964            self.config.dialect,
31965            Some(crate::dialects::DialectType::PostgreSQL)
31966                | Some(crate::dialects::DialectType::Redshift)
31967        );
31968        // Databricks and Spark don't output method names
31969        let is_databricks = matches!(
31970            self.config.dialect,
31971            Some(crate::dialects::DialectType::Databricks)
31972        );
31973        let is_spark = matches!(
31974            self.config.dialect,
31975            Some(crate::dialects::DialectType::Spark)
31976        );
31977        let suppress_method = is_databricks || is_spark || sample.suppress_method_output;
31978        // PostgreSQL always outputs BERNOULLI for BERNOULLI samples
31979        let force_method = is_postgres && matches!(sample.method, SampleMethod::Bernoulli);
31980        if !suppress_method && (sample.explicit_method || is_snowflake || force_method) {
31981            self.write_space();
31982            if !sample.explicit_method && (is_snowflake || force_method) {
31983                // Snowflake/PostgreSQL defaults to BERNOULLI when no method is specified
31984                self.write_keyword("BERNOULLI");
31985            } else {
31986                match sample.method {
31987                    SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
31988                    SampleMethod::System => self.write_keyword("SYSTEM"),
31989                    SampleMethod::Block => self.write_keyword("BLOCK"),
31990                    SampleMethod::Row => self.write_keyword("ROW"),
31991                    SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
31992                    SampleMethod::Percent => self.write_keyword("SYSTEM"),
31993                    SampleMethod::Bucket => {} // handled above
31994                }
31995            }
31996        }
31997
31998        // Output size, with or without parentheses depending on dialect
31999        let emit_size_no_parens = !self.config.tablesample_requires_parens;
32000        if emit_size_no_parens {
32001            self.write_space();
32002            match &sample.size {
32003                Expression::Tuple(tuple) => {
32004                    for (i, expr) in tuple.expressions.iter().enumerate() {
32005                        if i > 0 {
32006                            self.write(", ");
32007                        }
32008                        self.generate_expression(expr)?;
32009                    }
32010                }
32011                expr => self.generate_expression(expr)?,
32012            }
32013        } else {
32014            self.write(" (");
32015            self.generate_expression(&sample.size)?;
32016        }
32017
32018        // Determine unit
32019        let is_rows_method = matches!(
32020            sample.method,
32021            SampleMethod::Reservoir | SampleMethod::Row | SampleMethod::Bucket
32022        );
32023        let is_percent = matches!(
32024            sample.method,
32025            SampleMethod::Percent
32026                | SampleMethod::System
32027                | SampleMethod::Bernoulli
32028                | SampleMethod::Block
32029        );
32030
32031        // For Snowflake, PostgreSQL, and Presto/Trino, only output ROWS/PERCENT when the user explicitly wrote it (unit_after_size).
32032        // These dialects use bare numbers for percentage by default in TABLESAMPLE METHOD(size) syntax.
32033        // For Databricks and Spark, always output PERCENT for percentage samples.
32034        let is_presto = matches!(
32035            self.config.dialect,
32036            Some(crate::dialects::DialectType::Presto)
32037                | Some(crate::dialects::DialectType::Trino)
32038                | Some(crate::dialects::DialectType::Athena)
32039        );
32040        let should_output_unit = if is_databricks || is_spark {
32041            // Always output PERCENT for percentage-based methods, or ROWS for row-based methods
32042            is_percent || is_rows_method || sample.unit_after_size
32043        } else if is_snowflake || is_postgres || is_presto {
32044            sample.unit_after_size
32045        } else {
32046            sample.unit_after_size || (sample.explicit_method && (is_rows_method || is_percent))
32047        };
32048
32049        if should_output_unit {
32050            self.write_space();
32051            if sample.is_percent {
32052                self.write_keyword("PERCENT");
32053            } else if is_rows_method && !sample.unit_after_size {
32054                self.write_keyword("ROWS");
32055            } else if sample.unit_after_size {
32056                match sample.method {
32057                    SampleMethod::Percent
32058                    | SampleMethod::System
32059                    | SampleMethod::Bernoulli
32060                    | SampleMethod::Block => {
32061                        self.write_keyword("PERCENT");
32062                    }
32063                    SampleMethod::Row | SampleMethod::Reservoir => {
32064                        self.write_keyword("ROWS");
32065                    }
32066                    _ => self.write_keyword("ROWS"),
32067                }
32068            } else {
32069                self.write_keyword("PERCENT");
32070            }
32071        }
32072
32073        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
32074            if let Some(ref offset) = sample.offset {
32075                self.write_space();
32076                self.write_keyword("OFFSET");
32077                self.write_space();
32078                self.generate_expression(offset)?;
32079            }
32080        }
32081        if !emit_size_no_parens {
32082            self.write(")");
32083        }
32084
32085        Ok(())
32086    }
32087
32088    fn generate_sample_property(&mut self, e: &SampleProperty) -> Result<()> {
32089        // SAMPLE this (ClickHouse uses SAMPLE BY)
32090        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
32091            self.write_keyword("SAMPLE BY");
32092        } else {
32093            self.write_keyword("SAMPLE");
32094        }
32095        self.write_space();
32096        self.generate_expression(&e.this)?;
32097        Ok(())
32098    }
32099
32100    fn generate_schema(&mut self, e: &Schema) -> Result<()> {
32101        // this (expressions...)
32102        if let Some(this) = &e.this {
32103            self.generate_expression(this)?;
32104        }
32105        if !e.expressions.is_empty() {
32106            // Add space before column list if there's a preceding expression
32107            if e.this.is_some() {
32108                self.write_space();
32109            }
32110            self.write("(");
32111            for (i, expr) in e.expressions.iter().enumerate() {
32112                if i > 0 {
32113                    self.write(", ");
32114                }
32115                self.generate_expression(expr)?;
32116            }
32117            self.write(")");
32118        }
32119        Ok(())
32120    }
32121
32122    fn generate_schema_comment_property(&mut self, e: &SchemaCommentProperty) -> Result<()> {
32123        // COMMENT this
32124        self.write_keyword("COMMENT");
32125        self.write_space();
32126        self.generate_expression(&e.this)?;
32127        Ok(())
32128    }
32129
32130    fn generate_scope_resolution(&mut self, e: &ScopeResolution) -> Result<()> {
32131        // [this::]expression
32132        if let Some(this) = &e.this {
32133            self.generate_expression(this)?;
32134            self.write("::");
32135        }
32136        self.generate_expression(&e.expression)?;
32137        Ok(())
32138    }
32139
32140    fn generate_search(&mut self, e: &Search) -> Result<()> {
32141        // SEARCH(this, expression, [json_scope], [analyzer], [analyzer_options], [search_mode])
32142        self.write_keyword("SEARCH");
32143        self.write("(");
32144        self.generate_expression(&e.this)?;
32145        self.write(", ");
32146        self.generate_expression(&e.expression)?;
32147        if let Some(json_scope) = &e.json_scope {
32148            self.write(", ");
32149            self.generate_expression(json_scope)?;
32150        }
32151        if let Some(analyzer) = &e.analyzer {
32152            self.write(", ");
32153            self.generate_expression(analyzer)?;
32154        }
32155        if let Some(analyzer_options) = &e.analyzer_options {
32156            self.write(", ");
32157            self.generate_expression(analyzer_options)?;
32158        }
32159        if let Some(search_mode) = &e.search_mode {
32160            self.write(", ");
32161            self.generate_expression(search_mode)?;
32162        }
32163        self.write(")");
32164        Ok(())
32165    }
32166
32167    fn generate_search_ip(&mut self, e: &SearchIp) -> Result<()> {
32168        // SEARCH_IP(this, expression)
32169        self.write_keyword("SEARCH_IP");
32170        self.write("(");
32171        self.generate_expression(&e.this)?;
32172        self.write(", ");
32173        self.generate_expression(&e.expression)?;
32174        self.write(")");
32175        Ok(())
32176    }
32177
32178    fn generate_security_property(&mut self, e: &SecurityProperty) -> Result<()> {
32179        // SECURITY this
32180        self.write_keyword("SECURITY");
32181        self.write_space();
32182        self.generate_expression(&e.this)?;
32183        Ok(())
32184    }
32185
32186    fn generate_semantic_view(&mut self, e: &SemanticView) -> Result<()> {
32187        // SEMANTIC_VIEW(this [METRICS ...] [DIMENSIONS ...] [FACTS ...] [WHERE ...])
32188        self.write("SEMANTIC_VIEW(");
32189
32190        if self.config.pretty {
32191            // Pretty print: each clause on its own line
32192            self.write_newline();
32193            self.indent_level += 1;
32194            self.write_indent();
32195            self.generate_expression(&e.this)?;
32196
32197            if let Some(metrics) = &e.metrics {
32198                self.write_newline();
32199                self.write_indent();
32200                self.write_keyword("METRICS");
32201                self.write_space();
32202                self.generate_semantic_view_tuple(metrics)?;
32203            }
32204            if let Some(dimensions) = &e.dimensions {
32205                self.write_newline();
32206                self.write_indent();
32207                self.write_keyword("DIMENSIONS");
32208                self.write_space();
32209                self.generate_semantic_view_tuple(dimensions)?;
32210            }
32211            if let Some(facts) = &e.facts {
32212                self.write_newline();
32213                self.write_indent();
32214                self.write_keyword("FACTS");
32215                self.write_space();
32216                self.generate_semantic_view_tuple(facts)?;
32217            }
32218            if let Some(where_) = &e.where_ {
32219                self.write_newline();
32220                self.write_indent();
32221                self.write_keyword("WHERE");
32222                self.write_space();
32223                self.generate_expression(where_)?;
32224            }
32225            self.write_newline();
32226            self.indent_level -= 1;
32227            self.write_indent();
32228        } else {
32229            // Compact: all on one line
32230            self.generate_expression(&e.this)?;
32231            if let Some(metrics) = &e.metrics {
32232                self.write_space();
32233                self.write_keyword("METRICS");
32234                self.write_space();
32235                self.generate_semantic_view_tuple(metrics)?;
32236            }
32237            if let Some(dimensions) = &e.dimensions {
32238                self.write_space();
32239                self.write_keyword("DIMENSIONS");
32240                self.write_space();
32241                self.generate_semantic_view_tuple(dimensions)?;
32242            }
32243            if let Some(facts) = &e.facts {
32244                self.write_space();
32245                self.write_keyword("FACTS");
32246                self.write_space();
32247                self.generate_semantic_view_tuple(facts)?;
32248            }
32249            if let Some(where_) = &e.where_ {
32250                self.write_space();
32251                self.write_keyword("WHERE");
32252                self.write_space();
32253                self.generate_expression(where_)?;
32254            }
32255        }
32256        self.write(")");
32257        Ok(())
32258    }
32259
32260    /// Helper for SEMANTIC_VIEW tuple contents (without parentheses)
32261    fn generate_semantic_view_tuple(&mut self, expr: &Expression) -> Result<()> {
32262        if let Expression::Tuple(t) = expr {
32263            for (i, e) in t.expressions.iter().enumerate() {
32264                if i > 0 {
32265                    self.write(", ");
32266                }
32267                self.generate_expression(e)?;
32268            }
32269        } else {
32270            self.generate_expression(expr)?;
32271        }
32272        Ok(())
32273    }
32274
32275    fn generate_sequence_properties(&mut self, e: &SequenceProperties) -> Result<()> {
32276        // [START WITH start] [INCREMENT BY increment] [MINVALUE minvalue] [MAXVALUE maxvalue] [CACHE cache] [OWNED BY owned]
32277        if let Some(start) = &e.start {
32278            self.write_keyword("START WITH");
32279            self.write_space();
32280            self.generate_expression(start)?;
32281        }
32282        if let Some(increment) = &e.increment {
32283            self.write_space();
32284            self.write_keyword("INCREMENT BY");
32285            self.write_space();
32286            self.generate_expression(increment)?;
32287        }
32288        if let Some(minvalue) = &e.minvalue {
32289            self.write_space();
32290            self.write_keyword("MINVALUE");
32291            self.write_space();
32292            self.generate_expression(minvalue)?;
32293        }
32294        if let Some(maxvalue) = &e.maxvalue {
32295            self.write_space();
32296            self.write_keyword("MAXVALUE");
32297            self.write_space();
32298            self.generate_expression(maxvalue)?;
32299        }
32300        if let Some(cache) = &e.cache {
32301            self.write_space();
32302            self.write_keyword("CACHE");
32303            self.write_space();
32304            self.generate_expression(cache)?;
32305        }
32306        if let Some(owned) = &e.owned {
32307            self.write_space();
32308            self.write_keyword("OWNED BY");
32309            self.write_space();
32310            self.generate_expression(owned)?;
32311        }
32312        for opt in &e.options {
32313            self.write_space();
32314            self.generate_expression(opt)?;
32315        }
32316        Ok(())
32317    }
32318
32319    fn generate_serde_properties(&mut self, e: &SerdeProperties) -> Result<()> {
32320        // [WITH] SERDEPROPERTIES (expressions)
32321        if e.with_.is_some() {
32322            self.write_keyword("WITH");
32323            self.write_space();
32324        }
32325        self.write_keyword("SERDEPROPERTIES");
32326        self.write(" (");
32327        for (i, expr) in e.expressions.iter().enumerate() {
32328            if i > 0 {
32329                self.write(", ");
32330            }
32331            // Generate key=value without spaces around =
32332            match expr {
32333                Expression::Eq(eq) => {
32334                    self.generate_expression(&eq.left)?;
32335                    self.write("=");
32336                    self.generate_expression(&eq.right)?;
32337                }
32338                _ => self.generate_expression(expr)?,
32339            }
32340        }
32341        self.write(")");
32342        Ok(())
32343    }
32344
32345    fn generate_session_parameter(&mut self, e: &SessionParameter) -> Result<()> {
32346        // @@[kind.]this
32347        self.write("@@");
32348        if let Some(kind) = &e.kind {
32349            self.write(kind);
32350            self.write(".");
32351        }
32352        self.generate_expression(&e.this)?;
32353        Ok(())
32354    }
32355
32356    fn generate_set(&mut self, e: &Set) -> Result<()> {
32357        // SET/UNSET [TAG] expressions
32358        if e.unset.is_some() {
32359            self.write_keyword("UNSET");
32360        } else {
32361            self.write_keyword("SET");
32362        }
32363        if e.tag.is_some() {
32364            self.write_space();
32365            self.write_keyword("TAG");
32366        }
32367        if !e.expressions.is_empty() {
32368            self.write_space();
32369            for (i, expr) in e.expressions.iter().enumerate() {
32370                if i > 0 {
32371                    self.write(", ");
32372                }
32373                self.generate_expression(expr)?;
32374            }
32375        }
32376        Ok(())
32377    }
32378
32379    fn generate_set_config_property(&mut self, e: &SetConfigProperty) -> Result<()> {
32380        // SET this or SETCONFIG this
32381        self.write_keyword("SET");
32382        self.write_space();
32383        self.generate_expression(&e.this)?;
32384        Ok(())
32385    }
32386
32387    fn generate_set_item(&mut self, e: &SetItem) -> Result<()> {
32388        // [kind] name = value
32389        if let Some(kind) = &e.kind {
32390            self.write_keyword(kind);
32391            self.write_space();
32392        }
32393        self.generate_expression(&e.name)?;
32394        self.write(" = ");
32395        self.generate_expression(&e.value)?;
32396        Ok(())
32397    }
32398
32399    fn generate_set_operation(&mut self, e: &SetOperation) -> Result<()> {
32400        // [WITH ...] this UNION|INTERSECT|EXCEPT [ALL|DISTINCT] [BY NAME] expression
32401        if let Some(with_) = &e.with_ {
32402            self.generate_expression(with_)?;
32403            self.write_space();
32404        }
32405        self.generate_expression(&e.this)?;
32406        self.write_space();
32407        // kind should be UNION, INTERSECT, EXCEPT, etc.
32408        if let Some(kind) = &e.kind {
32409            self.write_keyword(kind);
32410        }
32411        if e.distinct {
32412            self.write_space();
32413            self.write_keyword("DISTINCT");
32414        } else {
32415            self.write_space();
32416            self.write_keyword("ALL");
32417        }
32418        if e.by_name.is_some() {
32419            self.write_space();
32420            self.write_keyword("BY NAME");
32421        }
32422        self.write_space();
32423        self.generate_expression(&e.expression)?;
32424        Ok(())
32425    }
32426
32427    fn generate_set_property(&mut self, e: &SetProperty) -> Result<()> {
32428        // SET or MULTISET
32429        if e.multi.is_some() {
32430            self.write_keyword("MULTISET");
32431        } else {
32432            self.write_keyword("SET");
32433        }
32434        Ok(())
32435    }
32436
32437    fn generate_settings_property(&mut self, e: &SettingsProperty) -> Result<()> {
32438        // SETTINGS expressions
32439        self.write_keyword("SETTINGS");
32440        if self.config.pretty && e.expressions.len() > 1 {
32441            // Pretty print: each setting on its own line, indented
32442            self.indent_level += 1;
32443            for (i, expr) in e.expressions.iter().enumerate() {
32444                if i > 0 {
32445                    self.write(",");
32446                }
32447                self.write_newline();
32448                self.write_indent();
32449                self.generate_expression(expr)?;
32450            }
32451            self.indent_level -= 1;
32452        } else {
32453            self.write_space();
32454            for (i, expr) in e.expressions.iter().enumerate() {
32455                if i > 0 {
32456                    self.write(", ");
32457                }
32458                self.generate_expression(expr)?;
32459            }
32460        }
32461        Ok(())
32462    }
32463
32464    fn generate_sharing_property(&mut self, e: &SharingProperty) -> Result<()> {
32465        // SHARING = this
32466        self.write_keyword("SHARING");
32467        if let Some(this) = &e.this {
32468            self.write(" = ");
32469            self.generate_expression(this)?;
32470        }
32471        Ok(())
32472    }
32473
32474    fn generate_slice(&mut self, e: &Slice) -> Result<()> {
32475        // Python array slicing: begin:end:step
32476        if let Some(begin) = &e.this {
32477            self.generate_expression(begin)?;
32478        }
32479        self.write(":");
32480        if let Some(end) = &e.expression {
32481            self.generate_expression(end)?;
32482        }
32483        if let Some(step) = &e.step {
32484            self.write(":");
32485            self.generate_expression(step)?;
32486        }
32487        Ok(())
32488    }
32489
32490    fn generate_sort_array(&mut self, e: &SortArray) -> Result<()> {
32491        // SORT_ARRAY(this, asc)
32492        self.write_keyword("SORT_ARRAY");
32493        self.write("(");
32494        self.generate_expression(&e.this)?;
32495        if let Some(asc) = &e.asc {
32496            self.write(", ");
32497            self.generate_expression(asc)?;
32498        }
32499        self.write(")");
32500        Ok(())
32501    }
32502
32503    fn generate_sort_by(&mut self, e: &SortBy) -> Result<()> {
32504        // SORT BY expressions
32505        self.write_keyword("SORT BY");
32506        self.write_space();
32507        for (i, expr) in e.expressions.iter().enumerate() {
32508            if i > 0 {
32509                self.write(", ");
32510            }
32511            self.generate_ordered(expr)?;
32512        }
32513        Ok(())
32514    }
32515
32516    fn generate_sort_key_property(&mut self, e: &SortKeyProperty) -> Result<()> {
32517        // [COMPOUND] SORTKEY(col1, col2, ...) - no space before paren
32518        if e.compound.is_some() {
32519            self.write_keyword("COMPOUND");
32520            self.write_space();
32521        }
32522        self.write_keyword("SORTKEY");
32523        self.write("(");
32524        // If this is a Tuple, unwrap its contents to avoid double parentheses
32525        if let Expression::Tuple(t) = e.this.as_ref() {
32526            for (i, expr) in t.expressions.iter().enumerate() {
32527                if i > 0 {
32528                    self.write(", ");
32529                }
32530                self.generate_expression(expr)?;
32531            }
32532        } else {
32533            self.generate_expression(&e.this)?;
32534        }
32535        self.write(")");
32536        Ok(())
32537    }
32538
32539    fn generate_split_part(&mut self, e: &SplitPart) -> Result<()> {
32540        // SPLIT_PART(this, delimiter, part_index)
32541        self.write_keyword("SPLIT_PART");
32542        self.write("(");
32543        self.generate_expression(&e.this)?;
32544        if let Some(delimiter) = &e.delimiter {
32545            self.write(", ");
32546            self.generate_expression(delimiter)?;
32547        }
32548        if let Some(part_index) = &e.part_index {
32549            self.write(", ");
32550            self.generate_expression(part_index)?;
32551        }
32552        self.write(")");
32553        Ok(())
32554    }
32555
32556    fn generate_sql_read_write_property(&mut self, e: &SqlReadWriteProperty) -> Result<()> {
32557        // READS SQL DATA or MODIFIES SQL DATA, etc.
32558        self.generate_expression(&e.this)?;
32559        Ok(())
32560    }
32561
32562    fn generate_sql_security_property(&mut self, e: &SqlSecurityProperty) -> Result<()> {
32563        // SQL SECURITY DEFINER or SQL SECURITY INVOKER
32564        self.write_keyword("SQL SECURITY");
32565        self.write_space();
32566        self.generate_expression(&e.this)?;
32567        Ok(())
32568    }
32569
32570    fn generate_st_distance(&mut self, e: &StDistance) -> Result<()> {
32571        // ST_DISTANCE(this, expression, [use_spheroid])
32572        self.write_keyword("ST_DISTANCE");
32573        self.write("(");
32574        self.generate_expression(&e.this)?;
32575        self.write(", ");
32576        self.generate_expression(&e.expression)?;
32577        if let Some(use_spheroid) = &e.use_spheroid {
32578            self.write(", ");
32579            self.generate_expression(use_spheroid)?;
32580        }
32581        self.write(")");
32582        Ok(())
32583    }
32584
32585    fn generate_st_point(&mut self, e: &StPoint) -> Result<()> {
32586        // ST_POINT(this, expression)
32587        self.write_keyword("ST_POINT");
32588        self.write("(");
32589        self.generate_expression(&e.this)?;
32590        self.write(", ");
32591        self.generate_expression(&e.expression)?;
32592        self.write(")");
32593        Ok(())
32594    }
32595
32596    fn generate_stability_property(&mut self, e: &StabilityProperty) -> Result<()> {
32597        // IMMUTABLE, STABLE, VOLATILE
32598        self.generate_expression(&e.this)?;
32599        Ok(())
32600    }
32601
32602    fn generate_standard_hash(&mut self, e: &StandardHash) -> Result<()> {
32603        // STANDARD_HASH(this, [expression])
32604        self.write_keyword("STANDARD_HASH");
32605        self.write("(");
32606        self.generate_expression(&e.this)?;
32607        if let Some(expression) = &e.expression {
32608            self.write(", ");
32609            self.generate_expression(expression)?;
32610        }
32611        self.write(")");
32612        Ok(())
32613    }
32614
32615    fn generate_storage_handler_property(&mut self, e: &StorageHandlerProperty) -> Result<()> {
32616        // STORED BY this
32617        self.write_keyword("STORED BY");
32618        self.write_space();
32619        self.generate_expression(&e.this)?;
32620        Ok(())
32621    }
32622
32623    fn generate_str_position(&mut self, e: &StrPosition) -> Result<()> {
32624        // STRPOS(this, substr) or STRPOS(this, substr, position)
32625        // Different dialects have different function names
32626        use crate::dialects::DialectType;
32627        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
32628            // Snowflake: CHARINDEX(substr, str[, position])
32629            self.write_keyword("CHARINDEX");
32630            self.write("(");
32631            if let Some(substr) = &e.substr {
32632                self.generate_expression(substr)?;
32633                self.write(", ");
32634            }
32635            self.generate_expression(&e.this)?;
32636            if let Some(position) = &e.position {
32637                self.write(", ");
32638                self.generate_expression(position)?;
32639            }
32640            self.write(")");
32641        } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
32642            self.write_keyword("POSITION");
32643            self.write("(");
32644            self.generate_expression(&e.this)?;
32645            if let Some(substr) = &e.substr {
32646                self.write(", ");
32647                self.generate_expression(substr)?;
32648            }
32649            if let Some(position) = &e.position {
32650                self.write(", ");
32651                self.generate_expression(position)?;
32652            }
32653            if let Some(occurrence) = &e.occurrence {
32654                self.write(", ");
32655                self.generate_expression(occurrence)?;
32656            }
32657            self.write(")");
32658        } else if matches!(
32659            self.config.dialect,
32660            Some(DialectType::SQLite)
32661                | Some(DialectType::Oracle)
32662                | Some(DialectType::BigQuery)
32663                | Some(DialectType::Teradata)
32664        ) {
32665            self.write_keyword("INSTR");
32666            self.write("(");
32667            self.generate_expression(&e.this)?;
32668            if let Some(substr) = &e.substr {
32669                self.write(", ");
32670                self.generate_expression(substr)?;
32671            }
32672            if let Some(position) = &e.position {
32673                self.write(", ");
32674                self.generate_expression(position)?;
32675            } else if e.occurrence.is_some() {
32676                // INSTR requires a position arg before occurrence: INSTR(str, substr, start, nth)
32677                // Default start position is 1
32678                self.write(", 1");
32679            }
32680            if let Some(occurrence) = &e.occurrence {
32681                self.write(", ");
32682                self.generate_expression(occurrence)?;
32683            }
32684            self.write(")");
32685        } else if matches!(
32686            self.config.dialect,
32687            Some(DialectType::MySQL)
32688                | Some(DialectType::SingleStore)
32689                | Some(DialectType::Doris)
32690                | Some(DialectType::StarRocks)
32691                | Some(DialectType::Hive)
32692                | Some(DialectType::Spark)
32693                | Some(DialectType::Databricks)
32694        ) {
32695            // LOCATE(substr, str[, position]) - substr first
32696            self.write_keyword("LOCATE");
32697            self.write("(");
32698            if let Some(substr) = &e.substr {
32699                self.generate_expression(substr)?;
32700                self.write(", ");
32701            }
32702            self.generate_expression(&e.this)?;
32703            if let Some(position) = &e.position {
32704                self.write(", ");
32705                self.generate_expression(position)?;
32706            }
32707            self.write(")");
32708        } else if matches!(self.config.dialect, Some(DialectType::TSQL)) {
32709            // CHARINDEX(substr, str[, position])
32710            self.write_keyword("CHARINDEX");
32711            self.write("(");
32712            if let Some(substr) = &e.substr {
32713                self.generate_expression(substr)?;
32714                self.write(", ");
32715            }
32716            self.generate_expression(&e.this)?;
32717            if let Some(position) = &e.position {
32718                self.write(", ");
32719                self.generate_expression(position)?;
32720            }
32721            self.write(")");
32722        } else if matches!(
32723            self.config.dialect,
32724            Some(DialectType::PostgreSQL)
32725                | Some(DialectType::Materialize)
32726                | Some(DialectType::RisingWave)
32727                | Some(DialectType::Redshift)
32728        ) {
32729            // POSITION(substr IN str) syntax
32730            self.write_keyword("POSITION");
32731            self.write("(");
32732            if let Some(substr) = &e.substr {
32733                self.generate_expression(substr)?;
32734                self.write(" IN ");
32735            }
32736            self.generate_expression(&e.this)?;
32737            self.write(")");
32738        } else {
32739            self.write_keyword("STRPOS");
32740            self.write("(");
32741            self.generate_expression(&e.this)?;
32742            if let Some(substr) = &e.substr {
32743                self.write(", ");
32744                self.generate_expression(substr)?;
32745            }
32746            if let Some(position) = &e.position {
32747                self.write(", ");
32748                self.generate_expression(position)?;
32749            }
32750            if let Some(occurrence) = &e.occurrence {
32751                self.write(", ");
32752                self.generate_expression(occurrence)?;
32753            }
32754            self.write(")");
32755        }
32756        Ok(())
32757    }
32758
32759    fn generate_str_to_date(&mut self, e: &StrToDate) -> Result<()> {
32760        match self.config.dialect {
32761            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
32762                // TO_DATE(this, java_format)
32763                self.write_keyword("TO_DATE");
32764                self.write("(");
32765                self.generate_expression(&e.this)?;
32766                if let Some(format) = &e.format {
32767                    self.write(", '");
32768                    self.write(&Self::strftime_to_java_format(format));
32769                    self.write("'");
32770                }
32771                self.write(")");
32772            }
32773            Some(DialectType::DuckDB) => {
32774                // CAST(STRPTIME(this, format) AS DATE)
32775                self.write_keyword("CAST");
32776                self.write("(");
32777                self.write_keyword("STRPTIME");
32778                self.write("(");
32779                self.generate_expression(&e.this)?;
32780                if let Some(format) = &e.format {
32781                    self.write(", '");
32782                    self.write(format);
32783                    self.write("'");
32784                }
32785                self.write(")");
32786                self.write_keyword(" AS ");
32787                self.write_keyword("DATE");
32788                self.write(")");
32789            }
32790            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
32791                // TO_DATE(this, pg_format)
32792                self.write_keyword("TO_DATE");
32793                self.write("(");
32794                self.generate_expression(&e.this)?;
32795                if let Some(format) = &e.format {
32796                    self.write(", '");
32797                    self.write(&Self::strftime_to_postgres_format(format));
32798                    self.write("'");
32799                }
32800                self.write(")");
32801            }
32802            Some(DialectType::BigQuery) => {
32803                // PARSE_DATE(format, this) - note: format comes first for BigQuery
32804                self.write_keyword("PARSE_DATE");
32805                self.write("(");
32806                if let Some(format) = &e.format {
32807                    self.write("'");
32808                    self.write(format);
32809                    self.write("'");
32810                    self.write(", ");
32811                }
32812                self.generate_expression(&e.this)?;
32813                self.write(")");
32814            }
32815            Some(DialectType::Teradata) => {
32816                // CAST(this AS DATE FORMAT 'teradata_fmt')
32817                self.write_keyword("CAST");
32818                self.write("(");
32819                self.generate_expression(&e.this)?;
32820                self.write_keyword(" AS ");
32821                self.write_keyword("DATE");
32822                if let Some(format) = &e.format {
32823                    self.write_keyword(" FORMAT ");
32824                    self.write("'");
32825                    self.write(&Self::strftime_to_teradata_format(format));
32826                    self.write("'");
32827                }
32828                self.write(")");
32829            }
32830            _ => {
32831                // STR_TO_DATE(this, format) - MySQL default
32832                self.write_keyword("STR_TO_DATE");
32833                self.write("(");
32834                self.generate_expression(&e.this)?;
32835                if let Some(format) = &e.format {
32836                    self.write(", '");
32837                    self.write(format);
32838                    self.write("'");
32839                }
32840                self.write(")");
32841            }
32842        }
32843        Ok(())
32844    }
32845
32846    /// Convert strftime format to Teradata date format (YYYY, DD, MM, etc.)
32847    fn strftime_to_teradata_format(fmt: &str) -> String {
32848        let mut result = fmt.to_string();
32849        result = result.replace("%Y", "YYYY");
32850        result = result.replace("%y", "YY");
32851        result = result.replace("%m", "MM");
32852        result = result.replace("%B", "MMMM");
32853        result = result.replace("%b", "MMM");
32854        result = result.replace("%d", "DD");
32855        result = result.replace("%j", "DDD");
32856        result = result.replace("%H", "HH");
32857        result = result.replace("%M", "MI");
32858        result = result.replace("%S", "SS");
32859        result = result.replace("%f", "SSSSSS");
32860        result = result.replace("%A", "EEEE");
32861        result = result.replace("%a", "EEE");
32862        result
32863    }
32864
32865    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
32866    /// Public static version for use by other modules
32867    pub fn strftime_to_java_format_static(fmt: &str) -> String {
32868        Self::strftime_to_java_format(fmt)
32869    }
32870
32871    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
32872    fn strftime_to_java_format(fmt: &str) -> String {
32873        let mut result = fmt.to_string();
32874        // Handle non-padded variants BEFORE their padded counterparts
32875        result = result.replace("%-d", "d");
32876        result = result.replace("%-m", "M");
32877        result = result.replace("%-H", "H");
32878        result = result.replace("%-M", "m");
32879        result = result.replace("%-S", "s");
32880        result = result.replace("%Y", "yyyy");
32881        result = result.replace("%y", "yy");
32882        result = result.replace("%m", "MM");
32883        result = result.replace("%B", "MMMM");
32884        result = result.replace("%b", "MMM");
32885        result = result.replace("%d", "dd");
32886        result = result.replace("%j", "DDD");
32887        result = result.replace("%H", "HH");
32888        result = result.replace("%M", "mm");
32889        result = result.replace("%S", "ss");
32890        result = result.replace("%f", "SSSSSS");
32891        result = result.replace("%A", "EEEE");
32892        result = result.replace("%a", "EEE");
32893        result
32894    }
32895
32896    /// Convert strftime format (%Y, %m, %d, etc.) to .NET date format for TSQL FORMAT()
32897    /// Similar to Java but uses ffffff for microseconds instead of SSSSSS
32898    fn strftime_to_tsql_format(fmt: &str) -> String {
32899        let mut result = fmt.to_string();
32900        // Handle non-padded variants BEFORE their padded counterparts
32901        result = result.replace("%-d", "d");
32902        result = result.replace("%-m", "M");
32903        result = result.replace("%-H", "H");
32904        result = result.replace("%-M", "m");
32905        result = result.replace("%-S", "s");
32906        result = result.replace("%Y", "yyyy");
32907        result = result.replace("%y", "yy");
32908        result = result.replace("%m", "MM");
32909        result = result.replace("%B", "MMMM");
32910        result = result.replace("%b", "MMM");
32911        result = result.replace("%d", "dd");
32912        result = result.replace("%j", "DDD");
32913        result = result.replace("%H", "HH");
32914        result = result.replace("%M", "mm");
32915        result = result.replace("%S", "ss");
32916        result = result.replace("%f", "ffffff");
32917        result = result.replace("%A", "dddd");
32918        result = result.replace("%a", "ddd");
32919        result
32920    }
32921
32922    /// Decompose a JSON path string like "$.y[0].z" into individual parts: ["y", "0", "z"]
32923    /// This is used for PostgreSQL/Redshift JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT
32924    fn decompose_json_path(path: &str) -> Vec<String> {
32925        let mut parts = Vec::new();
32926        // Strip leading $ and optional .
32927        let path = if path.starts_with("$.") {
32928            &path[2..]
32929        } else if path.starts_with('$') {
32930            &path[1..]
32931        } else {
32932            path
32933        };
32934        if path.is_empty() {
32935            return parts;
32936        }
32937        let mut current = String::new();
32938        let chars: Vec<char> = path.chars().collect();
32939        let mut i = 0;
32940        while i < chars.len() {
32941            match chars[i] {
32942                '.' => {
32943                    if !current.is_empty() {
32944                        parts.push(current.clone());
32945                        current.clear();
32946                    }
32947                    i += 1;
32948                }
32949                '[' => {
32950                    if !current.is_empty() {
32951                        parts.push(current.clone());
32952                        current.clear();
32953                    }
32954                    i += 1;
32955                    // Read the content inside brackets
32956                    let mut bracket_content = String::new();
32957                    while i < chars.len() && chars[i] != ']' {
32958                        // Skip quotes inside brackets
32959                        if chars[i] == '"' || chars[i] == '\'' {
32960                            let quote = chars[i];
32961                            i += 1;
32962                            while i < chars.len() && chars[i] != quote {
32963                                bracket_content.push(chars[i]);
32964                                i += 1;
32965                            }
32966                            if i < chars.len() {
32967                                i += 1;
32968                            } // skip closing quote
32969                        } else {
32970                            bracket_content.push(chars[i]);
32971                            i += 1;
32972                        }
32973                    }
32974                    if i < chars.len() {
32975                        i += 1;
32976                    } // skip ]
32977                      // Skip wildcard [*] - don't add as a part
32978                    if bracket_content != "*" {
32979                        parts.push(bracket_content);
32980                    }
32981                }
32982                _ => {
32983                    current.push(chars[i]);
32984                    i += 1;
32985                }
32986            }
32987        }
32988        if !current.is_empty() {
32989            parts.push(current);
32990        }
32991        parts
32992    }
32993
32994    /// Convert strftime format to PostgreSQL date format (YYYY, MM, DD, etc.)
32995    fn strftime_to_postgres_format(fmt: &str) -> String {
32996        let mut result = fmt.to_string();
32997        // Handle non-padded variants BEFORE their padded counterparts
32998        result = result.replace("%-d", "FMDD");
32999        result = result.replace("%-m", "FMMM");
33000        result = result.replace("%-H", "FMHH24");
33001        result = result.replace("%-M", "FMMI");
33002        result = result.replace("%-S", "FMSS");
33003        result = result.replace("%Y", "YYYY");
33004        result = result.replace("%y", "YY");
33005        result = result.replace("%m", "MM");
33006        result = result.replace("%B", "Month");
33007        result = result.replace("%b", "Mon");
33008        result = result.replace("%d", "DD");
33009        result = result.replace("%j", "DDD");
33010        result = result.replace("%H", "HH24");
33011        result = result.replace("%M", "MI");
33012        result = result.replace("%S", "SS");
33013        result = result.replace("%f", "US");
33014        result = result.replace("%A", "Day");
33015        result = result.replace("%a", "Dy");
33016        result
33017    }
33018
33019    /// Convert strftime format to Snowflake date format (yyyy, mm, DD, etc.)
33020    fn strftime_to_snowflake_format(fmt: &str) -> String {
33021        let mut result = fmt.to_string();
33022        // Handle %-d (non-padded day) before %d (padded day)
33023        result = result.replace("%-d", "dd");
33024        result = result.replace("%-m", "mm"); // non-padded month
33025        result = result.replace("%Y", "yyyy");
33026        result = result.replace("%y", "yy");
33027        result = result.replace("%m", "mm");
33028        result = result.replace("%d", "DD");
33029        result = result.replace("%H", "hh24");
33030        result = result.replace("%M", "mi");
33031        result = result.replace("%S", "ss");
33032        result = result.replace("%f", "ff");
33033        result
33034    }
33035
33036    fn generate_str_to_map(&mut self, e: &StrToMap) -> Result<()> {
33037        // STR_TO_MAP(this, pair_delim, key_value_delim)
33038        self.write_keyword("STR_TO_MAP");
33039        self.write("(");
33040        self.generate_expression(&e.this)?;
33041        // Spark/Hive: STR_TO_MAP needs explicit default delimiters
33042        let needs_defaults = matches!(
33043            self.config.dialect,
33044            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
33045        );
33046        if let Some(pair_delim) = &e.pair_delim {
33047            self.write(", ");
33048            self.generate_expression(pair_delim)?;
33049        } else if needs_defaults {
33050            self.write(", ','");
33051        }
33052        if let Some(key_value_delim) = &e.key_value_delim {
33053            self.write(", ");
33054            self.generate_expression(key_value_delim)?;
33055        } else if needs_defaults {
33056            self.write(", ':'");
33057        }
33058        self.write(")");
33059        Ok(())
33060    }
33061
33062    fn generate_str_to_time(&mut self, e: &StrToTime) -> Result<()> {
33063        // Detect format style: strftime (starts with %) vs Snowflake/Java
33064        let is_strftime = e.format.contains('%');
33065        // Helper: get strftime format from whatever style is stored
33066        let to_strftime = |f: &str| -> String {
33067            if is_strftime {
33068                f.to_string()
33069            } else {
33070                Self::snowflake_format_to_strftime(f)
33071            }
33072        };
33073        // Helper: get Java format
33074        let to_java = |f: &str| -> String {
33075            if is_strftime {
33076                Self::strftime_to_java_format(f)
33077            } else {
33078                Self::snowflake_format_to_spark(f)
33079            }
33080        };
33081        // Helper: get PG format
33082        let to_pg = |f: &str| -> String {
33083            if is_strftime {
33084                Self::strftime_to_postgres_format(f)
33085            } else {
33086                Self::convert_strptime_to_postgres_format(f)
33087            }
33088        };
33089
33090        match self.config.dialect {
33091            Some(DialectType::Exasol) => {
33092                self.write_keyword("TO_DATE");
33093                self.write("(");
33094                self.generate_expression(&e.this)?;
33095                self.write(", '");
33096                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
33097                self.write("'");
33098                self.write(")");
33099            }
33100            Some(DialectType::BigQuery) => {
33101                // BigQuery: PARSE_TIMESTAMP(format, value) - note swapped args
33102                let fmt = to_strftime(&e.format);
33103                // BigQuery normalizes: %Y-%m-%d -> %F, %H:%M:%S -> %T
33104                let fmt = fmt.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
33105                self.write_keyword("PARSE_TIMESTAMP");
33106                self.write("('");
33107                self.write(&fmt);
33108                self.write("', ");
33109                self.generate_expression(&e.this)?;
33110                self.write(")");
33111            }
33112            Some(DialectType::Hive) => {
33113                // Hive: CAST(x AS TIMESTAMP) for simple date formats
33114                // Check both the raw format and the converted format (in case it's already Java)
33115                let java_fmt = to_java(&e.format);
33116                if java_fmt == "yyyy-MM-dd HH:mm:ss"
33117                    || java_fmt == "yyyy-MM-dd"
33118                    || e.format == "yyyy-MM-dd HH:mm:ss"
33119                    || e.format == "yyyy-MM-dd"
33120                {
33121                    self.write_keyword("CAST");
33122                    self.write("(");
33123                    self.generate_expression(&e.this)?;
33124                    self.write(" ");
33125                    self.write_keyword("AS TIMESTAMP");
33126                    self.write(")");
33127                } else {
33128                    // CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, java_fmt)) AS TIMESTAMP)
33129                    self.write_keyword("CAST");
33130                    self.write("(");
33131                    self.write_keyword("FROM_UNIXTIME");
33132                    self.write("(");
33133                    self.write_keyword("UNIX_TIMESTAMP");
33134                    self.write("(");
33135                    self.generate_expression(&e.this)?;
33136                    self.write(", '");
33137                    self.write(&java_fmt);
33138                    self.write("')");
33139                    self.write(") ");
33140                    self.write_keyword("AS TIMESTAMP");
33141                    self.write(")");
33142                }
33143            }
33144            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
33145                // Spark: TO_TIMESTAMP(value, java_format)
33146                let java_fmt = to_java(&e.format);
33147                self.write_keyword("TO_TIMESTAMP");
33148                self.write("(");
33149                self.generate_expression(&e.this)?;
33150                self.write(", '");
33151                self.write(&java_fmt);
33152                self.write("')");
33153            }
33154            Some(DialectType::MySQL) => {
33155                // MySQL: STR_TO_DATE(value, format)
33156                let mut fmt = to_strftime(&e.format);
33157                // MySQL uses %e for non-padded day, %T for %H:%M:%S
33158                fmt = fmt.replace("%-d", "%e");
33159                fmt = fmt.replace("%-m", "%c");
33160                fmt = fmt.replace("%H:%M:%S", "%T");
33161                self.write_keyword("STR_TO_DATE");
33162                self.write("(");
33163                self.generate_expression(&e.this)?;
33164                self.write(", '");
33165                self.write(&fmt);
33166                self.write("')");
33167            }
33168            Some(DialectType::Drill) => {
33169                // Drill: TO_TIMESTAMP(value, java_format) with T quoted in single quotes
33170                let java_fmt = to_java(&e.format);
33171                // Drill quotes literal T character: T -> ''T'' (double-quoted within SQL string literal)
33172                let java_fmt = java_fmt.replace('T', "''T''");
33173                self.write_keyword("TO_TIMESTAMP");
33174                self.write("(");
33175                self.generate_expression(&e.this)?;
33176                self.write(", '");
33177                self.write(&java_fmt);
33178                self.write("')");
33179            }
33180            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
33181                // Presto: DATE_PARSE(value, strftime_format)
33182                let mut fmt = to_strftime(&e.format);
33183                // Presto uses %e for non-padded day, %T for %H:%M:%S
33184                fmt = fmt.replace("%-d", "%e");
33185                fmt = fmt.replace("%-m", "%c");
33186                fmt = fmt.replace("%H:%M:%S", "%T");
33187                self.write_keyword("DATE_PARSE");
33188                self.write("(");
33189                self.generate_expression(&e.this)?;
33190                self.write(", '");
33191                self.write(&fmt);
33192                self.write("')");
33193            }
33194            Some(DialectType::DuckDB) => {
33195                // DuckDB: STRPTIME(value, strftime_format)
33196                let fmt = to_strftime(&e.format);
33197                self.write_keyword("STRPTIME");
33198                self.write("(");
33199                self.generate_expression(&e.this)?;
33200                self.write(", '");
33201                self.write(&fmt);
33202                self.write("')");
33203            }
33204            Some(DialectType::PostgreSQL)
33205            | Some(DialectType::Redshift)
33206            | Some(DialectType::Materialize) => {
33207                // PostgreSQL/Redshift/Materialize: TO_TIMESTAMP(value, pg_format)
33208                let pg_fmt = to_pg(&e.format);
33209                self.write_keyword("TO_TIMESTAMP");
33210                self.write("(");
33211                self.generate_expression(&e.this)?;
33212                self.write(", '");
33213                self.write(&pg_fmt);
33214                self.write("')");
33215            }
33216            Some(DialectType::Oracle) => {
33217                // Oracle: TO_TIMESTAMP(value, pg_format)
33218                let pg_fmt = to_pg(&e.format);
33219                self.write_keyword("TO_TIMESTAMP");
33220                self.write("(");
33221                self.generate_expression(&e.this)?;
33222                self.write(", '");
33223                self.write(&pg_fmt);
33224                self.write("')");
33225            }
33226            Some(DialectType::Snowflake) => {
33227                // Snowflake: TO_TIMESTAMP(value, format) - native format
33228                self.write_keyword("TO_TIMESTAMP");
33229                self.write("(");
33230                self.generate_expression(&e.this)?;
33231                self.write(", '");
33232                self.write(&e.format);
33233                self.write("')");
33234            }
33235            _ => {
33236                // Default: STR_TO_TIME(this, format)
33237                self.write_keyword("STR_TO_TIME");
33238                self.write("(");
33239                self.generate_expression(&e.this)?;
33240                self.write(", '");
33241                self.write(&e.format);
33242                self.write("'");
33243                self.write(")");
33244            }
33245        }
33246        Ok(())
33247    }
33248
33249    /// Convert Snowflake normalized format to strftime-style (%Y, %m, etc.)
33250    fn snowflake_format_to_strftime(format: &str) -> String {
33251        let mut result = String::new();
33252        let chars: Vec<char> = format.chars().collect();
33253        let mut i = 0;
33254        while i < chars.len() {
33255            let remaining = &format[i..];
33256            if remaining.starts_with("yyyy") {
33257                result.push_str("%Y");
33258                i += 4;
33259            } else if remaining.starts_with("yy") {
33260                result.push_str("%y");
33261                i += 2;
33262            } else if remaining.starts_with("mmmm") {
33263                result.push_str("%B"); // full month name
33264                i += 4;
33265            } else if remaining.starts_with("mon") {
33266                result.push_str("%b"); // abbreviated month
33267                i += 3;
33268            } else if remaining.starts_with("mm") {
33269                result.push_str("%m");
33270                i += 2;
33271            } else if remaining.starts_with("DD") {
33272                result.push_str("%d");
33273                i += 2;
33274            } else if remaining.starts_with("dy") {
33275                result.push_str("%a"); // abbreviated day name
33276                i += 2;
33277            } else if remaining.starts_with("hh24") {
33278                result.push_str("%H");
33279                i += 4;
33280            } else if remaining.starts_with("hh12") {
33281                result.push_str("%I");
33282                i += 4;
33283            } else if remaining.starts_with("hh") {
33284                result.push_str("%H");
33285                i += 2;
33286            } else if remaining.starts_with("mi") {
33287                result.push_str("%M");
33288                i += 2;
33289            } else if remaining.starts_with("ss") {
33290                result.push_str("%S");
33291                i += 2;
33292            } else if remaining.starts_with("ff") {
33293                // Fractional seconds
33294                result.push_str("%f");
33295                i += 2;
33296                // Skip digits after ff (ff3, ff6, ff9)
33297                while i < chars.len() && chars[i].is_ascii_digit() {
33298                    i += 1;
33299                }
33300            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
33301                result.push_str("%p");
33302                i += 2;
33303            } else if remaining.starts_with("tz") {
33304                result.push_str("%Z");
33305                i += 2;
33306            } else {
33307                result.push(chars[i]);
33308                i += 1;
33309            }
33310        }
33311        result
33312    }
33313
33314    /// Convert Snowflake normalized format to Spark format (Java-style)
33315    fn snowflake_format_to_spark(format: &str) -> String {
33316        let mut result = String::new();
33317        let chars: Vec<char> = format.chars().collect();
33318        let mut i = 0;
33319        while i < chars.len() {
33320            let remaining = &format[i..];
33321            if remaining.starts_with("yyyy") {
33322                result.push_str("yyyy");
33323                i += 4;
33324            } else if remaining.starts_with("yy") {
33325                result.push_str("yy");
33326                i += 2;
33327            } else if remaining.starts_with("mmmm") {
33328                result.push_str("MMMM"); // full month name
33329                i += 4;
33330            } else if remaining.starts_with("mon") {
33331                result.push_str("MMM"); // abbreviated month
33332                i += 3;
33333            } else if remaining.starts_with("mm") {
33334                result.push_str("MM");
33335                i += 2;
33336            } else if remaining.starts_with("DD") {
33337                result.push_str("dd");
33338                i += 2;
33339            } else if remaining.starts_with("dy") {
33340                result.push_str("EEE"); // abbreviated day name
33341                i += 2;
33342            } else if remaining.starts_with("hh24") {
33343                result.push_str("HH");
33344                i += 4;
33345            } else if remaining.starts_with("hh12") {
33346                result.push_str("hh");
33347                i += 4;
33348            } else if remaining.starts_with("hh") {
33349                result.push_str("HH");
33350                i += 2;
33351            } else if remaining.starts_with("mi") {
33352                result.push_str("mm");
33353                i += 2;
33354            } else if remaining.starts_with("ss") {
33355                result.push_str("ss");
33356                i += 2;
33357            } else if remaining.starts_with("ff") {
33358                result.push_str("SSS"); // milliseconds
33359                i += 2;
33360                // Skip digits after ff
33361                while i < chars.len() && chars[i].is_ascii_digit() {
33362                    i += 1;
33363                }
33364            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
33365                result.push_str("a");
33366                i += 2;
33367            } else if remaining.starts_with("tz") {
33368                result.push_str("z");
33369                i += 2;
33370            } else {
33371                result.push(chars[i]);
33372                i += 1;
33373            }
33374        }
33375        result
33376    }
33377
33378    fn generate_str_to_unix(&mut self, e: &StrToUnix) -> Result<()> {
33379        match self.config.dialect {
33380            Some(DialectType::DuckDB) => {
33381                // DuckDB: EPOCH(STRPTIME(value, format))
33382                self.write_keyword("EPOCH");
33383                self.write("(");
33384                self.write_keyword("STRPTIME");
33385                self.write("(");
33386                if let Some(this) = &e.this {
33387                    self.generate_expression(this)?;
33388                }
33389                if let Some(format) = &e.format {
33390                    self.write(", '");
33391                    self.write(format);
33392                    self.write("'");
33393                }
33394                self.write("))");
33395            }
33396            Some(DialectType::Hive) => {
33397                // Hive: UNIX_TIMESTAMP(value, java_format) - convert C fmt to Java
33398                self.write_keyword("UNIX_TIMESTAMP");
33399                self.write("(");
33400                if let Some(this) = &e.this {
33401                    self.generate_expression(this)?;
33402                }
33403                if let Some(format) = &e.format {
33404                    let java_fmt = Self::strftime_to_java_format(format);
33405                    if java_fmt != "yyyy-MM-dd HH:mm:ss" {
33406                        self.write(", '");
33407                        self.write(&java_fmt);
33408                        self.write("'");
33409                    }
33410                }
33411                self.write(")");
33412            }
33413            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
33414                // Doris/StarRocks: UNIX_TIMESTAMP(value, format) - C format
33415                self.write_keyword("UNIX_TIMESTAMP");
33416                self.write("(");
33417                if let Some(this) = &e.this {
33418                    self.generate_expression(this)?;
33419                }
33420                if let Some(format) = &e.format {
33421                    self.write(", '");
33422                    self.write(format);
33423                    self.write("'");
33424                }
33425                self.write(")");
33426            }
33427            Some(DialectType::Presto) | Some(DialectType::Trino) => {
33428                // Presto: TO_UNIXTIME(COALESCE(TRY(DATE_PARSE(CAST(value AS VARCHAR), c_format)),
33429                //   PARSE_DATETIME(DATE_FORMAT(CAST(value AS TIMESTAMP), c_format), java_format)))
33430                let c_fmt = e.format.as_deref().unwrap_or("%Y-%m-%d %T");
33431                let java_fmt = Self::strftime_to_java_format(c_fmt);
33432                self.write_keyword("TO_UNIXTIME");
33433                self.write("(");
33434                self.write_keyword("COALESCE");
33435                self.write("(");
33436                self.write_keyword("TRY");
33437                self.write("(");
33438                self.write_keyword("DATE_PARSE");
33439                self.write("(");
33440                self.write_keyword("CAST");
33441                self.write("(");
33442                if let Some(this) = &e.this {
33443                    self.generate_expression(this)?;
33444                }
33445                self.write(" ");
33446                self.write_keyword("AS VARCHAR");
33447                self.write("), '");
33448                self.write(c_fmt);
33449                self.write("')), ");
33450                self.write_keyword("PARSE_DATETIME");
33451                self.write("(");
33452                self.write_keyword("DATE_FORMAT");
33453                self.write("(");
33454                self.write_keyword("CAST");
33455                self.write("(");
33456                if let Some(this) = &e.this {
33457                    self.generate_expression(this)?;
33458                }
33459                self.write(" ");
33460                self.write_keyword("AS TIMESTAMP");
33461                self.write("), '");
33462                self.write(c_fmt);
33463                self.write("'), '");
33464                self.write(&java_fmt);
33465                self.write("')))");
33466            }
33467            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
33468                // Spark: UNIX_TIMESTAMP(value, java_format)
33469                self.write_keyword("UNIX_TIMESTAMP");
33470                self.write("(");
33471                if let Some(this) = &e.this {
33472                    self.generate_expression(this)?;
33473                }
33474                if let Some(format) = &e.format {
33475                    let java_fmt = Self::strftime_to_java_format(format);
33476                    self.write(", '");
33477                    self.write(&java_fmt);
33478                    self.write("'");
33479                }
33480                self.write(")");
33481            }
33482            _ => {
33483                // Default: STR_TO_UNIX(this, format)
33484                self.write_keyword("STR_TO_UNIX");
33485                self.write("(");
33486                if let Some(this) = &e.this {
33487                    self.generate_expression(this)?;
33488                }
33489                if let Some(format) = &e.format {
33490                    self.write(", '");
33491                    self.write(format);
33492                    self.write("'");
33493                }
33494                self.write(")");
33495            }
33496        }
33497        Ok(())
33498    }
33499
33500    fn generate_string_to_array(&mut self, e: &StringToArray) -> Result<()> {
33501        // STRING_TO_ARRAY(this, delimiter, null_string)
33502        self.write_keyword("STRING_TO_ARRAY");
33503        self.write("(");
33504        self.generate_expression(&e.this)?;
33505        if let Some(expression) = &e.expression {
33506            self.write(", ");
33507            self.generate_expression(expression)?;
33508        }
33509        if let Some(null_val) = &e.null {
33510            self.write(", ");
33511            self.generate_expression(null_val)?;
33512        }
33513        self.write(")");
33514        Ok(())
33515    }
33516
33517    fn generate_struct(&mut self, e: &Struct) -> Result<()> {
33518        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
33519            // Snowflake: OBJECT_CONSTRUCT('key', value, 'key', value, ...)
33520            self.write_keyword("OBJECT_CONSTRUCT");
33521            self.write("(");
33522            for (i, (name, expr)) in e.fields.iter().enumerate() {
33523                if i > 0 {
33524                    self.write(", ");
33525                }
33526                if let Some(name) = name {
33527                    self.write("'");
33528                    self.write(name);
33529                    self.write("'");
33530                    self.write(", ");
33531                } else {
33532                    self.write("'_");
33533                    self.write(&i.to_string());
33534                    self.write("'");
33535                    self.write(", ");
33536                }
33537                self.generate_expression(expr)?;
33538            }
33539            self.write(")");
33540        } else if self.config.struct_curly_brace_notation {
33541            // DuckDB-style: {'key': value, ...}
33542            self.write("{");
33543            for (i, (name, expr)) in e.fields.iter().enumerate() {
33544                if i > 0 {
33545                    self.write(", ");
33546                }
33547                if let Some(name) = name {
33548                    // Quote the key as a string literal
33549                    self.write("'");
33550                    self.write(name);
33551                    self.write("'");
33552                    self.write(": ");
33553                } else {
33554                    // Unnamed field: use positional key
33555                    self.write("'_");
33556                    self.write(&i.to_string());
33557                    self.write("'");
33558                    self.write(": ");
33559                }
33560                self.generate_expression(expr)?;
33561            }
33562            self.write("}");
33563        } else {
33564            // Standard SQL struct notation
33565            // BigQuery/Spark/Databricks use: STRUCT(value AS name, ...)
33566            // Others (Presto etc.) use: STRUCT(name AS value, ...) or ROW(value, ...)
33567            let value_as_name = matches!(
33568                self.config.dialect,
33569                Some(DialectType::BigQuery)
33570                    | Some(DialectType::Spark)
33571                    | Some(DialectType::Databricks)
33572                    | Some(DialectType::Hive)
33573            );
33574            self.write_keyword("STRUCT");
33575            self.write("(");
33576            for (i, (name, expr)) in e.fields.iter().enumerate() {
33577                if i > 0 {
33578                    self.write(", ");
33579                }
33580                if let Some(name) = name {
33581                    if value_as_name {
33582                        // STRUCT(value AS name)
33583                        self.generate_expression(expr)?;
33584                        self.write_space();
33585                        self.write_keyword("AS");
33586                        self.write_space();
33587                        // Quote name if it contains spaces or special chars
33588                        let needs_quoting = name.contains(' ') || name.contains('-');
33589                        if needs_quoting {
33590                            if matches!(
33591                                self.config.dialect,
33592                                Some(DialectType::Spark)
33593                                    | Some(DialectType::Databricks)
33594                                    | Some(DialectType::Hive)
33595                            ) {
33596                                self.write("`");
33597                                self.write(name);
33598                                self.write("`");
33599                            } else {
33600                                self.write(name);
33601                            }
33602                        } else {
33603                            self.write(name);
33604                        }
33605                    } else {
33606                        // STRUCT(name AS value)
33607                        self.write(name);
33608                        self.write_space();
33609                        self.write_keyword("AS");
33610                        self.write_space();
33611                        self.generate_expression(expr)?;
33612                    }
33613                } else {
33614                    self.generate_expression(expr)?;
33615                }
33616            }
33617            self.write(")");
33618        }
33619        Ok(())
33620    }
33621
33622    fn generate_stuff(&mut self, e: &Stuff) -> Result<()> {
33623        // STUFF(this, start, length, expression)
33624        self.write_keyword("STUFF");
33625        self.write("(");
33626        self.generate_expression(&e.this)?;
33627        if let Some(start) = &e.start {
33628            self.write(", ");
33629            self.generate_expression(start)?;
33630        }
33631        if let Some(length) = e.length {
33632            self.write(", ");
33633            self.write(&length.to_string());
33634        }
33635        self.write(", ");
33636        self.generate_expression(&e.expression)?;
33637        self.write(")");
33638        Ok(())
33639    }
33640
33641    fn generate_substring_index(&mut self, e: &SubstringIndex) -> Result<()> {
33642        // SUBSTRING_INDEX(this, delimiter, count)
33643        self.write_keyword("SUBSTRING_INDEX");
33644        self.write("(");
33645        self.generate_expression(&e.this)?;
33646        if let Some(delimiter) = &e.delimiter {
33647            self.write(", ");
33648            self.generate_expression(delimiter)?;
33649        }
33650        if let Some(count) = &e.count {
33651            self.write(", ");
33652            self.generate_expression(count)?;
33653        }
33654        self.write(")");
33655        Ok(())
33656    }
33657
33658    fn generate_summarize(&mut self, e: &Summarize) -> Result<()> {
33659        // SUMMARIZE [TABLE] this
33660        self.write_keyword("SUMMARIZE");
33661        if e.table.is_some() {
33662            self.write_space();
33663            self.write_keyword("TABLE");
33664        }
33665        self.write_space();
33666        self.generate_expression(&e.this)?;
33667        Ok(())
33668    }
33669
33670    fn generate_systimestamp(&mut self, _e: &Systimestamp) -> Result<()> {
33671        // SYSTIMESTAMP
33672        self.write_keyword("SYSTIMESTAMP");
33673        Ok(())
33674    }
33675
33676    fn generate_table_alias(&mut self, e: &TableAlias) -> Result<()> {
33677        // alias (columns...)
33678        if let Some(this) = &e.this {
33679            self.generate_expression(this)?;
33680        }
33681        if !e.columns.is_empty() {
33682            self.write("(");
33683            for (i, col) in e.columns.iter().enumerate() {
33684                if i > 0 {
33685                    self.write(", ");
33686                }
33687                self.generate_expression(col)?;
33688            }
33689            self.write(")");
33690        }
33691        Ok(())
33692    }
33693
33694    fn generate_table_from_rows(&mut self, e: &TableFromRows) -> Result<()> {
33695        // TABLE(this) [AS alias]
33696        self.write_keyword("TABLE");
33697        self.write("(");
33698        self.generate_expression(&e.this)?;
33699        self.write(")");
33700        if let Some(alias) = &e.alias {
33701            self.write_space();
33702            self.write_keyword("AS");
33703            self.write_space();
33704            self.write(alias);
33705        }
33706        Ok(())
33707    }
33708
33709    fn generate_rows_from(&mut self, e: &RowsFrom) -> Result<()> {
33710        // ROWS FROM (func1(...) AS alias1(...), func2(...) AS alias2(...)) [WITH ORDINALITY] [AS alias(...)]
33711        self.write_keyword("ROWS FROM");
33712        self.write(" (");
33713        for (i, expr) in e.expressions.iter().enumerate() {
33714            if i > 0 {
33715                self.write(", ");
33716            }
33717            // Each expression is either:
33718            // - A plain function (no alias)
33719            // - A Tuple(function, TableAlias) for: FUNC() AS alias(col type, ...)
33720            match expr {
33721                Expression::Tuple(tuple) if tuple.expressions.len() == 2 => {
33722                    // First element is the function, second is the TableAlias
33723                    self.generate_expression(&tuple.expressions[0])?;
33724                    self.write_space();
33725                    self.write_keyword("AS");
33726                    self.write_space();
33727                    self.generate_expression(&tuple.expressions[1])?;
33728                }
33729                _ => {
33730                    self.generate_expression(expr)?;
33731                }
33732            }
33733        }
33734        self.write(")");
33735        if e.ordinality {
33736            self.write_space();
33737            self.write_keyword("WITH ORDINALITY");
33738        }
33739        if let Some(alias) = &e.alias {
33740            self.write_space();
33741            self.write_keyword("AS");
33742            self.write_space();
33743            self.generate_expression(alias)?;
33744        }
33745        Ok(())
33746    }
33747
33748    fn generate_table_sample(&mut self, e: &TableSample) -> Result<()> {
33749        use crate::dialects::DialectType;
33750
33751        // New wrapper pattern: expression + Sample struct
33752        if let (Some(this), Some(sample)) = (&e.this, &e.sample) {
33753            // For alias_post_tablesample dialects (Spark, Hive, Oracle): output base expr, TABLESAMPLE, then alias
33754            if self.config.alias_post_tablesample {
33755                // Handle Subquery with alias and Alias wrapper
33756                if let Expression::Subquery(ref s) = **this {
33757                    if let Some(ref alias) = s.alias {
33758                        // Create a clone without alias for output
33759                        let mut subquery_no_alias = (**s).clone();
33760                        subquery_no_alias.alias = None;
33761                        subquery_no_alias.column_aliases = Vec::new();
33762                        self.generate_expression(&Expression::Subquery(Box::new(
33763                            subquery_no_alias,
33764                        )))?;
33765                        self.write_space();
33766                        self.write_keyword("TABLESAMPLE");
33767                        self.generate_sample_body(sample)?;
33768                        if let Some(ref seed) = sample.seed {
33769                            self.write_space();
33770                            let use_seed = sample.use_seed_keyword
33771                                && !matches!(
33772                                    self.config.dialect,
33773                                    Some(crate::dialects::DialectType::Databricks)
33774                                        | Some(crate::dialects::DialectType::Spark)
33775                                );
33776                            if use_seed {
33777                                self.write_keyword("SEED");
33778                            } else {
33779                                self.write_keyword("REPEATABLE");
33780                            }
33781                            self.write(" (");
33782                            self.generate_expression(seed)?;
33783                            self.write(")");
33784                        }
33785                        self.write_space();
33786                        self.write_keyword("AS");
33787                        self.write_space();
33788                        self.generate_identifier(alias)?;
33789                        return Ok(());
33790                    }
33791                } else if let Expression::Alias(ref a) = **this {
33792                    // Output the base expression without alias
33793                    self.generate_expression(&a.this)?;
33794                    self.write_space();
33795                    self.write_keyword("TABLESAMPLE");
33796                    self.generate_sample_body(sample)?;
33797                    if let Some(ref seed) = sample.seed {
33798                        self.write_space();
33799                        let use_seed = sample.use_seed_keyword
33800                            && !matches!(
33801                                self.config.dialect,
33802                                Some(crate::dialects::DialectType::Databricks)
33803                                    | Some(crate::dialects::DialectType::Spark)
33804                            );
33805                        if use_seed {
33806                            self.write_keyword("SEED");
33807                        } else {
33808                            self.write_keyword("REPEATABLE");
33809                        }
33810                        self.write(" (");
33811                        self.generate_expression(seed)?;
33812                        self.write(")");
33813                    }
33814                    // Output alias after TABLESAMPLE
33815                    self.write_space();
33816                    self.write_keyword("AS");
33817                    self.write_space();
33818                    self.generate_identifier(&a.alias)?;
33819                    return Ok(());
33820                }
33821            }
33822            // Default: generate wrapped expression first, then TABLESAMPLE
33823            self.generate_expression(this)?;
33824            self.write_space();
33825            self.write_keyword("TABLESAMPLE");
33826            self.generate_sample_body(sample)?;
33827            // Seed for table-level sample
33828            if let Some(ref seed) = sample.seed {
33829                self.write_space();
33830                // Databricks uses REPEATABLE, not SEED
33831                let use_seed = sample.use_seed_keyword
33832                    && !matches!(
33833                        self.config.dialect,
33834                        Some(crate::dialects::DialectType::Databricks)
33835                            | Some(crate::dialects::DialectType::Spark)
33836                    );
33837                if use_seed {
33838                    self.write_keyword("SEED");
33839                } else {
33840                    self.write_keyword("REPEATABLE");
33841                }
33842                self.write(" (");
33843                self.generate_expression(seed)?;
33844                self.write(")");
33845            }
33846            return Ok(());
33847        }
33848
33849        // Legacy pattern: TABLESAMPLE [method] (expressions) or TABLESAMPLE method BUCKET numerator OUT OF denominator
33850        self.write_keyword("TABLESAMPLE");
33851        if let Some(method) = &e.method {
33852            self.write_space();
33853            self.write_keyword(method);
33854        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
33855            // Snowflake defaults to BERNOULLI when no method is specified
33856            self.write_space();
33857            self.write_keyword("BERNOULLI");
33858        }
33859        if let (Some(numerator), Some(denominator)) = (&e.bucket_numerator, &e.bucket_denominator) {
33860            self.write_space();
33861            self.write_keyword("BUCKET");
33862            self.write_space();
33863            self.generate_expression(numerator)?;
33864            self.write_space();
33865            self.write_keyword("OUT OF");
33866            self.write_space();
33867            self.generate_expression(denominator)?;
33868            if let Some(field) = &e.bucket_field {
33869                self.write_space();
33870                self.write_keyword("ON");
33871                self.write_space();
33872                self.generate_expression(field)?;
33873            }
33874        } else if !e.expressions.is_empty() {
33875            self.write(" (");
33876            for (i, expr) in e.expressions.iter().enumerate() {
33877                if i > 0 {
33878                    self.write(", ");
33879                }
33880                self.generate_expression(expr)?;
33881            }
33882            self.write(")");
33883        } else if let Some(percent) = &e.percent {
33884            self.write(" (");
33885            self.generate_expression(percent)?;
33886            self.write_space();
33887            self.write_keyword("PERCENT");
33888            self.write(")");
33889        }
33890        Ok(())
33891    }
33892
33893    fn generate_tag(&mut self, e: &Tag) -> Result<()> {
33894        // [prefix]this[postfix]
33895        if let Some(prefix) = &e.prefix {
33896            self.generate_expression(prefix)?;
33897        }
33898        if let Some(this) = &e.this {
33899            self.generate_expression(this)?;
33900        }
33901        if let Some(postfix) = &e.postfix {
33902            self.generate_expression(postfix)?;
33903        }
33904        Ok(())
33905    }
33906
33907    fn generate_tags(&mut self, e: &Tags) -> Result<()> {
33908        // TAG (expressions)
33909        self.write_keyword("TAG");
33910        self.write(" (");
33911        for (i, expr) in e.expressions.iter().enumerate() {
33912            if i > 0 {
33913                self.write(", ");
33914            }
33915            self.generate_expression(expr)?;
33916        }
33917        self.write(")");
33918        Ok(())
33919    }
33920
33921    fn generate_temporary_property(&mut self, e: &TemporaryProperty) -> Result<()> {
33922        // TEMPORARY or TEMP or [this] TEMPORARY
33923        if let Some(this) = &e.this {
33924            self.generate_expression(this)?;
33925            self.write_space();
33926        }
33927        self.write_keyword("TEMPORARY");
33928        Ok(())
33929    }
33930
33931    /// Generate a Time function expression
33932    /// For most dialects: TIME('value')
33933    fn generate_time_func(&mut self, e: &UnaryFunc) -> Result<()> {
33934        // Standard: TIME(value)
33935        self.write_keyword("TIME");
33936        self.write("(");
33937        self.generate_expression(&e.this)?;
33938        self.write(")");
33939        Ok(())
33940    }
33941
33942    fn generate_time_add(&mut self, e: &TimeAdd) -> Result<()> {
33943        // TIME_ADD(this, expression, unit)
33944        self.write_keyword("TIME_ADD");
33945        self.write("(");
33946        self.generate_expression(&e.this)?;
33947        self.write(", ");
33948        self.generate_expression(&e.expression)?;
33949        if let Some(unit) = &e.unit {
33950            self.write(", ");
33951            self.write_keyword(unit);
33952        }
33953        self.write(")");
33954        Ok(())
33955    }
33956
33957    fn generate_time_diff(&mut self, e: &TimeDiff) -> Result<()> {
33958        // TIME_DIFF(this, expression, unit)
33959        self.write_keyword("TIME_DIFF");
33960        self.write("(");
33961        self.generate_expression(&e.this)?;
33962        self.write(", ");
33963        self.generate_expression(&e.expression)?;
33964        if let Some(unit) = &e.unit {
33965            self.write(", ");
33966            self.write_keyword(unit);
33967        }
33968        self.write(")");
33969        Ok(())
33970    }
33971
33972    fn generate_time_from_parts(&mut self, e: &TimeFromParts) -> Result<()> {
33973        // TIME_FROM_PARTS(hour, minute, second, nanosecond)
33974        self.write_keyword("TIME_FROM_PARTS");
33975        self.write("(");
33976        let mut first = true;
33977        if let Some(hour) = &e.hour {
33978            self.generate_expression(hour)?;
33979            first = false;
33980        }
33981        if let Some(minute) = &e.min {
33982            if !first {
33983                self.write(", ");
33984            }
33985            self.generate_expression(minute)?;
33986            first = false;
33987        }
33988        if let Some(second) = &e.sec {
33989            if !first {
33990                self.write(", ");
33991            }
33992            self.generate_expression(second)?;
33993            first = false;
33994        }
33995        if let Some(ns) = &e.nano {
33996            if !first {
33997                self.write(", ");
33998            }
33999            self.generate_expression(ns)?;
34000        }
34001        self.write(")");
34002        Ok(())
34003    }
34004
34005    fn generate_time_slice(&mut self, e: &TimeSlice) -> Result<()> {
34006        // TIME_SLICE(this, expression, unit)
34007        self.write_keyword("TIME_SLICE");
34008        self.write("(");
34009        self.generate_expression(&e.this)?;
34010        self.write(", ");
34011        self.generate_expression(&e.expression)?;
34012        self.write(", ");
34013        self.write_keyword(&e.unit);
34014        self.write(")");
34015        Ok(())
34016    }
34017
34018    fn generate_time_str_to_time(&mut self, e: &TimeStrToTime) -> Result<()> {
34019        // TIME_STR_TO_TIME(this)
34020        self.write_keyword("TIME_STR_TO_TIME");
34021        self.write("(");
34022        self.generate_expression(&e.this)?;
34023        self.write(")");
34024        Ok(())
34025    }
34026
34027    fn generate_time_sub(&mut self, e: &TimeSub) -> Result<()> {
34028        // TIME_SUB(this, expression, unit)
34029        self.write_keyword("TIME_SUB");
34030        self.write("(");
34031        self.generate_expression(&e.this)?;
34032        self.write(", ");
34033        self.generate_expression(&e.expression)?;
34034        if let Some(unit) = &e.unit {
34035            self.write(", ");
34036            self.write_keyword(unit);
34037        }
34038        self.write(")");
34039        Ok(())
34040    }
34041
34042    fn generate_time_to_str(&mut self, e: &TimeToStr) -> Result<()> {
34043        match self.config.dialect {
34044            Some(DialectType::Exasol) => {
34045                // Exasol uses TO_CHAR with Exasol-specific format
34046                self.write_keyword("TO_CHAR");
34047                self.write("(");
34048                self.generate_expression(&e.this)?;
34049                self.write(", '");
34050                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
34051                self.write("'");
34052                self.write(")");
34053            }
34054            Some(DialectType::PostgreSQL)
34055            | Some(DialectType::Redshift)
34056            | Some(DialectType::Materialize) => {
34057                // PostgreSQL/Redshift/Materialize uses TO_CHAR with PG-specific format
34058                self.write_keyword("TO_CHAR");
34059                self.write("(");
34060                self.generate_expression(&e.this)?;
34061                self.write(", '");
34062                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
34063                self.write("'");
34064                self.write(")");
34065            }
34066            Some(DialectType::Oracle) => {
34067                // Oracle uses TO_CHAR with PG-like format
34068                self.write_keyword("TO_CHAR");
34069                self.write("(");
34070                self.generate_expression(&e.this)?;
34071                self.write(", '");
34072                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
34073                self.write("'");
34074                self.write(")");
34075            }
34076            Some(DialectType::Drill) => {
34077                // Drill: TO_CHAR with Java format
34078                self.write_keyword("TO_CHAR");
34079                self.write("(");
34080                self.generate_expression(&e.this)?;
34081                self.write(", '");
34082                self.write(&Self::strftime_to_java_format(&e.format));
34083                self.write("'");
34084                self.write(")");
34085            }
34086            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
34087                // TSQL: FORMAT(value, format) with .NET-style format
34088                self.write_keyword("FORMAT");
34089                self.write("(");
34090                self.generate_expression(&e.this)?;
34091                self.write(", '");
34092                self.write(&Self::strftime_to_tsql_format(&e.format));
34093                self.write("'");
34094                self.write(")");
34095            }
34096            Some(DialectType::DuckDB) => {
34097                // DuckDB: STRFTIME(value, format) - keeps C format
34098                self.write_keyword("STRFTIME");
34099                self.write("(");
34100                self.generate_expression(&e.this)?;
34101                self.write(", '");
34102                self.write(&e.format);
34103                self.write("'");
34104                self.write(")");
34105            }
34106            Some(DialectType::BigQuery) => {
34107                // BigQuery: FORMAT_DATE(format, value) - note swapped arg order
34108                // Normalize: %Y-%m-%d -> %F, %H:%M:%S -> %T
34109                let fmt = e.format.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
34110                self.write_keyword("FORMAT_DATE");
34111                self.write("('");
34112                self.write(&fmt);
34113                self.write("', ");
34114                self.generate_expression(&e.this)?;
34115                self.write(")");
34116            }
34117            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
34118                // Hive/Spark: DATE_FORMAT(value, java_format)
34119                self.write_keyword("DATE_FORMAT");
34120                self.write("(");
34121                self.generate_expression(&e.this)?;
34122                self.write(", '");
34123                self.write(&Self::strftime_to_java_format(&e.format));
34124                self.write("'");
34125                self.write(")");
34126            }
34127            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
34128                // Presto/Trino: DATE_FORMAT(value, format) - keeps C format
34129                self.write_keyword("DATE_FORMAT");
34130                self.write("(");
34131                self.generate_expression(&e.this)?;
34132                self.write(", '");
34133                self.write(&e.format);
34134                self.write("'");
34135                self.write(")");
34136            }
34137            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
34138                // Doris/StarRocks: DATE_FORMAT(value, format) - keeps C format
34139                self.write_keyword("DATE_FORMAT");
34140                self.write("(");
34141                self.generate_expression(&e.this)?;
34142                self.write(", '");
34143                self.write(&e.format);
34144                self.write("'");
34145                self.write(")");
34146            }
34147            _ => {
34148                // Default: TIME_TO_STR(this, format)
34149                self.write_keyword("TIME_TO_STR");
34150                self.write("(");
34151                self.generate_expression(&e.this)?;
34152                self.write(", '");
34153                self.write(&e.format);
34154                self.write("'");
34155                self.write(")");
34156            }
34157        }
34158        Ok(())
34159    }
34160
34161    fn generate_time_to_unix(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
34162        match self.config.dialect {
34163            Some(DialectType::DuckDB) => {
34164                // DuckDB: EPOCH(x)
34165                self.write_keyword("EPOCH");
34166                self.write("(");
34167                self.generate_expression(&e.this)?;
34168                self.write(")");
34169            }
34170            Some(DialectType::Hive)
34171            | Some(DialectType::Spark)
34172            | Some(DialectType::Databricks)
34173            | Some(DialectType::Doris)
34174            | Some(DialectType::StarRocks)
34175            | Some(DialectType::Drill) => {
34176                // Hive/Spark/Doris/StarRocks/Drill: UNIX_TIMESTAMP(x)
34177                self.write_keyword("UNIX_TIMESTAMP");
34178                self.write("(");
34179                self.generate_expression(&e.this)?;
34180                self.write(")");
34181            }
34182            Some(DialectType::Presto) | Some(DialectType::Trino) => {
34183                // Presto: TO_UNIXTIME(x)
34184                self.write_keyword("TO_UNIXTIME");
34185                self.write("(");
34186                self.generate_expression(&e.this)?;
34187                self.write(")");
34188            }
34189            _ => {
34190                // Default: TIME_TO_UNIX(x)
34191                self.write_keyword("TIME_TO_UNIX");
34192                self.write("(");
34193                self.generate_expression(&e.this)?;
34194                self.write(")");
34195            }
34196        }
34197        Ok(())
34198    }
34199
34200    fn generate_time_str_to_date(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
34201        match self.config.dialect {
34202            Some(DialectType::Hive) => {
34203                // Hive: TO_DATE(x)
34204                self.write_keyword("TO_DATE");
34205                self.write("(");
34206                self.generate_expression(&e.this)?;
34207                self.write(")");
34208            }
34209            _ => {
34210                // Default: TIME_STR_TO_DATE(x)
34211                self.write_keyword("TIME_STR_TO_DATE");
34212                self.write("(");
34213                self.generate_expression(&e.this)?;
34214                self.write(")");
34215            }
34216        }
34217        Ok(())
34218    }
34219
34220    fn generate_time_trunc(&mut self, e: &TimeTrunc) -> Result<()> {
34221        // TIME_TRUNC(this, unit)
34222        self.write_keyword("TIME_TRUNC");
34223        self.write("(");
34224        self.generate_expression(&e.this)?;
34225        self.write(", ");
34226        self.write_keyword(&e.unit);
34227        self.write(")");
34228        Ok(())
34229    }
34230
34231    fn generate_time_unit(&mut self, e: &TimeUnit) -> Result<()> {
34232        // Just output the unit name
34233        if let Some(unit) = &e.unit {
34234            self.write_keyword(unit);
34235        }
34236        Ok(())
34237    }
34238
34239    /// Generate a Timestamp function expression
34240    /// For Exasol: {ts'value'} -> TO_TIMESTAMP('value')
34241    /// For other dialects: TIMESTAMP('value')
34242    fn generate_timestamp_func(&mut self, e: &TimestampFunc) -> Result<()> {
34243        use crate::dialects::DialectType;
34244        use crate::expressions::Literal;
34245
34246        match self.config.dialect {
34247            // Exasol uses TO_TIMESTAMP for Timestamp expressions
34248            Some(DialectType::Exasol) => {
34249                self.write_keyword("TO_TIMESTAMP");
34250                self.write("(");
34251                // Extract the string value from the expression if it's a string literal
34252                if let Some(this) = &e.this {
34253                    match this.as_ref() {
34254                        Expression::Literal(Literal::String(s)) => {
34255                            self.write("'");
34256                            self.write(s);
34257                            self.write("'");
34258                        }
34259                        _ => {
34260                            self.generate_expression(this)?;
34261                        }
34262                    }
34263                }
34264                self.write(")");
34265            }
34266            // Standard: TIMESTAMP(value) or TIMESTAMP(value, zone)
34267            _ => {
34268                self.write_keyword("TIMESTAMP");
34269                self.write("(");
34270                if let Some(this) = &e.this {
34271                    self.generate_expression(this)?;
34272                }
34273                if let Some(zone) = &e.zone {
34274                    self.write(", ");
34275                    self.generate_expression(zone)?;
34276                }
34277                self.write(")");
34278            }
34279        }
34280        Ok(())
34281    }
34282
34283    fn generate_timestamp_add(&mut self, e: &TimestampAdd) -> Result<()> {
34284        // TIMESTAMP_ADD(this, expression, unit)
34285        self.write_keyword("TIMESTAMP_ADD");
34286        self.write("(");
34287        self.generate_expression(&e.this)?;
34288        self.write(", ");
34289        self.generate_expression(&e.expression)?;
34290        if let Some(unit) = &e.unit {
34291            self.write(", ");
34292            self.write_keyword(unit);
34293        }
34294        self.write(")");
34295        Ok(())
34296    }
34297
34298    fn generate_timestamp_diff(&mut self, e: &TimestampDiff) -> Result<()> {
34299        // TIMESTAMP_DIFF(this, expression, unit)
34300        self.write_keyword("TIMESTAMP_DIFF");
34301        self.write("(");
34302        self.generate_expression(&e.this)?;
34303        self.write(", ");
34304        self.generate_expression(&e.expression)?;
34305        if let Some(unit) = &e.unit {
34306            self.write(", ");
34307            self.write_keyword(unit);
34308        }
34309        self.write(")");
34310        Ok(())
34311    }
34312
34313    fn generate_timestamp_from_parts(&mut self, e: &TimestampFromParts) -> Result<()> {
34314        // TIMESTAMP_FROM_PARTS(this, expression)
34315        self.write_keyword("TIMESTAMP_FROM_PARTS");
34316        self.write("(");
34317        if let Some(this) = &e.this {
34318            self.generate_expression(this)?;
34319        }
34320        if let Some(expression) = &e.expression {
34321            self.write(", ");
34322            self.generate_expression(expression)?;
34323        }
34324        if let Some(zone) = &e.zone {
34325            self.write(", ");
34326            self.generate_expression(zone)?;
34327        }
34328        if let Some(milli) = &e.milli {
34329            self.write(", ");
34330            self.generate_expression(milli)?;
34331        }
34332        self.write(")");
34333        Ok(())
34334    }
34335
34336    fn generate_timestamp_sub(&mut self, e: &TimestampSub) -> Result<()> {
34337        // TIMESTAMP_SUB(this, INTERVAL expression unit)
34338        self.write_keyword("TIMESTAMP_SUB");
34339        self.write("(");
34340        self.generate_expression(&e.this)?;
34341        self.write(", ");
34342        self.write_keyword("INTERVAL");
34343        self.write_space();
34344        self.generate_expression(&e.expression)?;
34345        if let Some(unit) = &e.unit {
34346            self.write_space();
34347            self.write_keyword(unit);
34348        }
34349        self.write(")");
34350        Ok(())
34351    }
34352
34353    fn generate_timestamp_tz_from_parts(&mut self, e: &TimestampTzFromParts) -> Result<()> {
34354        // TIMESTAMP_TZ_FROM_PARTS(...)
34355        self.write_keyword("TIMESTAMP_TZ_FROM_PARTS");
34356        self.write("(");
34357        if let Some(zone) = &e.zone {
34358            self.generate_expression(zone)?;
34359        }
34360        self.write(")");
34361        Ok(())
34362    }
34363
34364    fn generate_to_binary(&mut self, e: &ToBinary) -> Result<()> {
34365        // TO_BINARY(this, [format])
34366        self.write_keyword("TO_BINARY");
34367        self.write("(");
34368        self.generate_expression(&e.this)?;
34369        if let Some(format) = &e.format {
34370            self.write(", '");
34371            self.write(format);
34372            self.write("'");
34373        }
34374        self.write(")");
34375        Ok(())
34376    }
34377
34378    fn generate_to_boolean(&mut self, e: &ToBoolean) -> Result<()> {
34379        // TO_BOOLEAN(this)
34380        self.write_keyword("TO_BOOLEAN");
34381        self.write("(");
34382        self.generate_expression(&e.this)?;
34383        self.write(")");
34384        Ok(())
34385    }
34386
34387    fn generate_to_char(&mut self, e: &ToChar) -> Result<()> {
34388        // TO_CHAR(this, [format], [nlsparam])
34389        self.write_keyword("TO_CHAR");
34390        self.write("(");
34391        self.generate_expression(&e.this)?;
34392        if let Some(format) = &e.format {
34393            self.write(", '");
34394            self.write(format);
34395            self.write("'");
34396        }
34397        if let Some(nlsparam) = &e.nlsparam {
34398            self.write(", ");
34399            self.generate_expression(nlsparam)?;
34400        }
34401        self.write(")");
34402        Ok(())
34403    }
34404
34405    fn generate_to_decfloat(&mut self, e: &ToDecfloat) -> Result<()> {
34406        // TO_DECFLOAT(this, [format])
34407        self.write_keyword("TO_DECFLOAT");
34408        self.write("(");
34409        self.generate_expression(&e.this)?;
34410        if let Some(format) = &e.format {
34411            self.write(", '");
34412            self.write(format);
34413            self.write("'");
34414        }
34415        self.write(")");
34416        Ok(())
34417    }
34418
34419    fn generate_to_double(&mut self, e: &ToDouble) -> Result<()> {
34420        // TO_DOUBLE(this, [format])
34421        self.write_keyword("TO_DOUBLE");
34422        self.write("(");
34423        self.generate_expression(&e.this)?;
34424        if let Some(format) = &e.format {
34425            self.write(", '");
34426            self.write(format);
34427            self.write("'");
34428        }
34429        self.write(")");
34430        Ok(())
34431    }
34432
34433    fn generate_to_file(&mut self, e: &ToFile) -> Result<()> {
34434        // TO_FILE(this, path)
34435        self.write_keyword("TO_FILE");
34436        self.write("(");
34437        self.generate_expression(&e.this)?;
34438        if let Some(path) = &e.path {
34439            self.write(", ");
34440            self.generate_expression(path)?;
34441        }
34442        self.write(")");
34443        Ok(())
34444    }
34445
34446    fn generate_to_number(&mut self, e: &ToNumber) -> Result<()> {
34447        // TO_NUMBER or TRY_TO_NUMBER (this, [format], [precision], [scale])
34448        // If safe flag is set, output TRY_TO_NUMBER
34449        let is_safe = e.safe.is_some();
34450        if is_safe {
34451            self.write_keyword("TRY_TO_NUMBER");
34452        } else {
34453            self.write_keyword("TO_NUMBER");
34454        }
34455        self.write("(");
34456        self.generate_expression(&e.this)?;
34457        if let Some(format) = &e.format {
34458            self.write(", ");
34459            self.generate_expression(format)?;
34460        }
34461        if let Some(nlsparam) = &e.nlsparam {
34462            self.write(", ");
34463            self.generate_expression(nlsparam)?;
34464        }
34465        if let Some(precision) = &e.precision {
34466            self.write(", ");
34467            self.generate_expression(precision)?;
34468        }
34469        if let Some(scale) = &e.scale {
34470            self.write(", ");
34471            self.generate_expression(scale)?;
34472        }
34473        self.write(")");
34474        Ok(())
34475    }
34476
34477    fn generate_to_table_property(&mut self, e: &ToTableProperty) -> Result<()> {
34478        // TO_TABLE this
34479        self.write_keyword("TO_TABLE");
34480        self.write_space();
34481        self.generate_expression(&e.this)?;
34482        Ok(())
34483    }
34484
34485    fn generate_transaction(&mut self, e: &Transaction) -> Result<()> {
34486        // Check mark to determine the format
34487        let mark_text = e.mark.as_ref().map(|m| match m.as_ref() {
34488            Expression::Identifier(id) => id.name.clone(),
34489            Expression::Literal(Literal::String(s)) => s.clone(),
34490            _ => String::new(),
34491        });
34492
34493        let is_start = mark_text.as_ref().map_or(false, |s| s == "START");
34494        let has_transaction_keyword = mark_text.as_ref().map_or(false, |s| s == "TRANSACTION");
34495        let has_with_mark = e.mark.as_ref().map_or(false, |m| {
34496            matches!(m.as_ref(), Expression::Literal(Literal::String(_)))
34497        });
34498
34499        // For Presto/Trino: always use START TRANSACTION
34500        let use_start_transaction = matches!(
34501            self.config.dialect,
34502            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
34503        );
34504        // For most dialects: strip TRANSACTION keyword
34505        let strip_transaction = matches!(
34506            self.config.dialect,
34507            Some(DialectType::Snowflake)
34508                | Some(DialectType::PostgreSQL)
34509                | Some(DialectType::Redshift)
34510                | Some(DialectType::MySQL)
34511                | Some(DialectType::Hive)
34512                | Some(DialectType::Spark)
34513                | Some(DialectType::Databricks)
34514                | Some(DialectType::DuckDB)
34515                | Some(DialectType::Oracle)
34516                | Some(DialectType::Doris)
34517                | Some(DialectType::StarRocks)
34518                | Some(DialectType::Materialize)
34519                | Some(DialectType::ClickHouse)
34520        );
34521
34522        if is_start || use_start_transaction {
34523            // START TRANSACTION [modes]
34524            self.write_keyword("START TRANSACTION");
34525            if let Some(modes) = &e.modes {
34526                self.write_space();
34527                self.generate_expression(modes)?;
34528            }
34529        } else {
34530            // BEGIN [DEFERRED|IMMEDIATE|EXCLUSIVE] [TRANSACTION] [transaction_name] [WITH MARK 'desc']
34531            self.write_keyword("BEGIN");
34532
34533            // Check if `this` is a transaction kind (DEFERRED/IMMEDIATE/EXCLUSIVE)
34534            let is_kind = e.this.as_ref().map_or(false, |t| {
34535                if let Expression::Identifier(id) = t.as_ref() {
34536                    matches!(
34537                        id.name.to_uppercase().as_str(),
34538                        "DEFERRED" | "IMMEDIATE" | "EXCLUSIVE"
34539                    )
34540                } else {
34541                    false
34542                }
34543            });
34544
34545            // Output kind before TRANSACTION keyword
34546            if is_kind {
34547                if let Some(this) = &e.this {
34548                    self.write_space();
34549                    if let Expression::Identifier(id) = this.as_ref() {
34550                        self.write_keyword(&id.name);
34551                    }
34552                }
34553            }
34554
34555            // Output TRANSACTION keyword if it was present and target supports it
34556            if (has_transaction_keyword || has_with_mark) && !strip_transaction {
34557                self.write_space();
34558                self.write_keyword("TRANSACTION");
34559            }
34560
34561            // Output transaction name (not kind)
34562            if !is_kind {
34563                if let Some(this) = &e.this {
34564                    self.write_space();
34565                    self.generate_expression(this)?;
34566                }
34567            }
34568
34569            // Output WITH MARK 'description' for TSQL
34570            if has_with_mark {
34571                self.write_space();
34572                self.write_keyword("WITH MARK");
34573                if let Some(Expression::Literal(Literal::String(desc))) = e.mark.as_deref() {
34574                    if !desc.is_empty() {
34575                        self.write_space();
34576                        self.write(&format!("'{}'", desc));
34577                    }
34578                }
34579            }
34580
34581            // Output modes (isolation levels, etc.)
34582            if let Some(modes) = &e.modes {
34583                self.write_space();
34584                self.generate_expression(modes)?;
34585            }
34586        }
34587        Ok(())
34588    }
34589
34590    fn generate_transform(&mut self, e: &Transform) -> Result<()> {
34591        // TRANSFORM(this, expression)
34592        self.write_keyword("TRANSFORM");
34593        self.write("(");
34594        self.generate_expression(&e.this)?;
34595        self.write(", ");
34596        self.generate_expression(&e.expression)?;
34597        self.write(")");
34598        Ok(())
34599    }
34600
34601    fn generate_transform_model_property(&mut self, e: &TransformModelProperty) -> Result<()> {
34602        // TRANSFORM(expressions)
34603        self.write_keyword("TRANSFORM");
34604        self.write("(");
34605        if self.config.pretty && !e.expressions.is_empty() {
34606            self.indent_level += 1;
34607            for (i, expr) in e.expressions.iter().enumerate() {
34608                if i > 0 {
34609                    self.write(",");
34610                }
34611                self.write_newline();
34612                self.write_indent();
34613                self.generate_expression(expr)?;
34614            }
34615            self.indent_level -= 1;
34616            self.write_newline();
34617            self.write(")");
34618        } else {
34619            for (i, expr) in e.expressions.iter().enumerate() {
34620                if i > 0 {
34621                    self.write(", ");
34622                }
34623                self.generate_expression(expr)?;
34624            }
34625            self.write(")");
34626        }
34627        Ok(())
34628    }
34629
34630    fn generate_transient_property(&mut self, e: &TransientProperty) -> Result<()> {
34631        use crate::dialects::DialectType;
34632        // TRANSIENT is Snowflake-specific; skip for other dialects
34633        if let Some(this) = &e.this {
34634            self.generate_expression(this)?;
34635            if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
34636                self.write_space();
34637            }
34638        }
34639        if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
34640            self.write_keyword("TRANSIENT");
34641        }
34642        Ok(())
34643    }
34644
34645    fn generate_translate(&mut self, e: &Translate) -> Result<()> {
34646        // TRANSLATE(this, from_, to)
34647        self.write_keyword("TRANSLATE");
34648        self.write("(");
34649        self.generate_expression(&e.this)?;
34650        if let Some(from) = &e.from_ {
34651            self.write(", ");
34652            self.generate_expression(from)?;
34653        }
34654        if let Some(to) = &e.to {
34655            self.write(", ");
34656            self.generate_expression(to)?;
34657        }
34658        self.write(")");
34659        Ok(())
34660    }
34661
34662    fn generate_translate_characters(&mut self, e: &TranslateCharacters) -> Result<()> {
34663        // TRANSLATE(this USING expression)
34664        self.write_keyword("TRANSLATE");
34665        self.write("(");
34666        self.generate_expression(&e.this)?;
34667        self.write_space();
34668        self.write_keyword("USING");
34669        self.write_space();
34670        self.generate_expression(&e.expression)?;
34671        if e.with_error.is_some() {
34672            self.write_space();
34673            self.write_keyword("WITH ERROR");
34674        }
34675        self.write(")");
34676        Ok(())
34677    }
34678
34679    fn generate_truncate_table(&mut self, e: &TruncateTable) -> Result<()> {
34680        // TRUNCATE TABLE table1, table2, ...
34681        self.write_keyword("TRUNCATE TABLE");
34682        self.write_space();
34683        for (i, expr) in e.expressions.iter().enumerate() {
34684            if i > 0 {
34685                self.write(", ");
34686            }
34687            self.generate_expression(expr)?;
34688        }
34689        Ok(())
34690    }
34691
34692    fn generate_try_base64_decode_binary(&mut self, e: &TryBase64DecodeBinary) -> Result<()> {
34693        // TRY_BASE64_DECODE_BINARY(this, [alphabet])
34694        self.write_keyword("TRY_BASE64_DECODE_BINARY");
34695        self.write("(");
34696        self.generate_expression(&e.this)?;
34697        if let Some(alphabet) = &e.alphabet {
34698            self.write(", ");
34699            self.generate_expression(alphabet)?;
34700        }
34701        self.write(")");
34702        Ok(())
34703    }
34704
34705    fn generate_try_base64_decode_string(&mut self, e: &TryBase64DecodeString) -> Result<()> {
34706        // TRY_BASE64_DECODE_STRING(this, [alphabet])
34707        self.write_keyword("TRY_BASE64_DECODE_STRING");
34708        self.write("(");
34709        self.generate_expression(&e.this)?;
34710        if let Some(alphabet) = &e.alphabet {
34711            self.write(", ");
34712            self.generate_expression(alphabet)?;
34713        }
34714        self.write(")");
34715        Ok(())
34716    }
34717
34718    fn generate_try_to_decfloat(&mut self, e: &TryToDecfloat) -> Result<()> {
34719        // TRY_TO_DECFLOAT(this, [format])
34720        self.write_keyword("TRY_TO_DECFLOAT");
34721        self.write("(");
34722        self.generate_expression(&e.this)?;
34723        if let Some(format) = &e.format {
34724            self.write(", '");
34725            self.write(format);
34726            self.write("'");
34727        }
34728        self.write(")");
34729        Ok(())
34730    }
34731
34732    fn generate_ts_or_ds_add(&mut self, e: &TsOrDsAdd) -> Result<()> {
34733        // TS_OR_DS_ADD(this, expression, [unit], [return_type])
34734        self.write_keyword("TS_OR_DS_ADD");
34735        self.write("(");
34736        self.generate_expression(&e.this)?;
34737        self.write(", ");
34738        self.generate_expression(&e.expression)?;
34739        if let Some(unit) = &e.unit {
34740            self.write(", ");
34741            self.write_keyword(unit);
34742        }
34743        if let Some(return_type) = &e.return_type {
34744            self.write(", ");
34745            self.generate_expression(return_type)?;
34746        }
34747        self.write(")");
34748        Ok(())
34749    }
34750
34751    fn generate_ts_or_ds_diff(&mut self, e: &TsOrDsDiff) -> Result<()> {
34752        // TS_OR_DS_DIFF(this, expression, [unit])
34753        self.write_keyword("TS_OR_DS_DIFF");
34754        self.write("(");
34755        self.generate_expression(&e.this)?;
34756        self.write(", ");
34757        self.generate_expression(&e.expression)?;
34758        if let Some(unit) = &e.unit {
34759            self.write(", ");
34760            self.write_keyword(unit);
34761        }
34762        self.write(")");
34763        Ok(())
34764    }
34765
34766    fn generate_ts_or_ds_to_date(&mut self, e: &TsOrDsToDate) -> Result<()> {
34767        let default_time_format = "%Y-%m-%d %H:%M:%S";
34768        let default_date_format = "%Y-%m-%d";
34769        let has_non_default_format = e.format.as_ref().map_or(false, |f| {
34770            f != default_time_format && f != default_date_format
34771        });
34772
34773        if has_non_default_format {
34774            // With non-default format: dialect-specific handling
34775            let fmt = e.format.as_ref().unwrap();
34776            match self.config.dialect {
34777                Some(DialectType::MySQL) | Some(DialectType::StarRocks) => {
34778                    // MySQL/StarRocks: STR_TO_DATE(x, fmt) - no CAST wrapper
34779                    // STR_TO_DATE is the MySQL-native form of StrToTime
34780                    let str_to_time = crate::expressions::StrToTime {
34781                        this: Box::new((*e.this).clone()),
34782                        format: fmt.clone(),
34783                        zone: None,
34784                        safe: None,
34785                        target_type: None,
34786                    };
34787                    self.generate_str_to_time(&str_to_time)?;
34788                }
34789                Some(DialectType::Hive)
34790                | Some(DialectType::Spark)
34791                | Some(DialectType::Databricks) => {
34792                    // Hive/Spark: TO_DATE(x, java_fmt)
34793                    self.write_keyword("TO_DATE");
34794                    self.write("(");
34795                    self.generate_expression(&e.this)?;
34796                    self.write(", '");
34797                    self.write(&Self::strftime_to_java_format(fmt));
34798                    self.write("')");
34799                }
34800                Some(DialectType::Snowflake) => {
34801                    // Snowflake: TO_DATE(x, snowflake_fmt)
34802                    self.write_keyword("TO_DATE");
34803                    self.write("(");
34804                    self.generate_expression(&e.this)?;
34805                    self.write(", '");
34806                    self.write(&Self::strftime_to_snowflake_format(fmt));
34807                    self.write("')");
34808                }
34809                Some(DialectType::Doris) => {
34810                    // Doris: TO_DATE(x) - ignores format
34811                    self.write_keyword("TO_DATE");
34812                    self.write("(");
34813                    self.generate_expression(&e.this)?;
34814                    self.write(")");
34815                }
34816                _ => {
34817                    // Default: CAST(STR_TO_TIME(x, fmt) AS DATE)
34818                    self.write_keyword("CAST");
34819                    self.write("(");
34820                    let str_to_time = crate::expressions::StrToTime {
34821                        this: Box::new((*e.this).clone()),
34822                        format: fmt.clone(),
34823                        zone: None,
34824                        safe: None,
34825                        target_type: None,
34826                    };
34827                    self.generate_str_to_time(&str_to_time)?;
34828                    self.write_keyword(" AS ");
34829                    self.write_keyword("DATE");
34830                    self.write(")");
34831                }
34832            }
34833        } else {
34834            // Without format (or default format): simple date conversion
34835            match self.config.dialect {
34836                Some(DialectType::MySQL)
34837                | Some(DialectType::SQLite)
34838                | Some(DialectType::StarRocks) => {
34839                    // MySQL/SQLite/StarRocks: DATE(x)
34840                    self.write_keyword("DATE");
34841                    self.write("(");
34842                    self.generate_expression(&e.this)?;
34843                    self.write(")");
34844                }
34845                Some(DialectType::Hive)
34846                | Some(DialectType::Spark)
34847                | Some(DialectType::Databricks)
34848                | Some(DialectType::Snowflake)
34849                | Some(DialectType::Doris) => {
34850                    // Hive/Spark/Databricks/Snowflake/Doris: TO_DATE(x)
34851                    self.write_keyword("TO_DATE");
34852                    self.write("(");
34853                    self.generate_expression(&e.this)?;
34854                    self.write(")");
34855                }
34856                Some(DialectType::Presto)
34857                | Some(DialectType::Trino)
34858                | Some(DialectType::Athena) => {
34859                    // Presto/Trino: CAST(CAST(x AS TIMESTAMP) AS DATE)
34860                    self.write_keyword("CAST");
34861                    self.write("(");
34862                    self.write_keyword("CAST");
34863                    self.write("(");
34864                    self.generate_expression(&e.this)?;
34865                    self.write_keyword(" AS ");
34866                    self.write_keyword("TIMESTAMP");
34867                    self.write(")");
34868                    self.write_keyword(" AS ");
34869                    self.write_keyword("DATE");
34870                    self.write(")");
34871                }
34872                Some(DialectType::ClickHouse) => {
34873                    // ClickHouse: CAST(x AS Nullable(DATE))
34874                    self.write_keyword("CAST");
34875                    self.write("(");
34876                    self.generate_expression(&e.this)?;
34877                    self.write_keyword(" AS ");
34878                    self.write("Nullable(DATE)");
34879                    self.write(")");
34880                }
34881                _ => {
34882                    // Default: CAST(x AS DATE)
34883                    self.write_keyword("CAST");
34884                    self.write("(");
34885                    self.generate_expression(&e.this)?;
34886                    self.write_keyword(" AS ");
34887                    self.write_keyword("DATE");
34888                    self.write(")");
34889                }
34890            }
34891        }
34892        Ok(())
34893    }
34894
34895    fn generate_ts_or_ds_to_time(&mut self, e: &TsOrDsToTime) -> Result<()> {
34896        // TS_OR_DS_TO_TIME(this, [format])
34897        self.write_keyword("TS_OR_DS_TO_TIME");
34898        self.write("(");
34899        self.generate_expression(&e.this)?;
34900        if let Some(format) = &e.format {
34901            self.write(", '");
34902            self.write(format);
34903            self.write("'");
34904        }
34905        self.write(")");
34906        Ok(())
34907    }
34908
34909    fn generate_unhex(&mut self, e: &Unhex) -> Result<()> {
34910        // UNHEX(this, [expression])
34911        self.write_keyword("UNHEX");
34912        self.write("(");
34913        self.generate_expression(&e.this)?;
34914        if let Some(expression) = &e.expression {
34915            self.write(", ");
34916            self.generate_expression(expression)?;
34917        }
34918        self.write(")");
34919        Ok(())
34920    }
34921
34922    fn generate_unicode_string(&mut self, e: &UnicodeString) -> Result<()> {
34923        // U&this [UESCAPE escape]
34924        self.write("U&");
34925        self.generate_expression(&e.this)?;
34926        if let Some(escape) = &e.escape {
34927            self.write_space();
34928            self.write_keyword("UESCAPE");
34929            self.write_space();
34930            self.generate_expression(escape)?;
34931        }
34932        Ok(())
34933    }
34934
34935    fn generate_uniform(&mut self, e: &Uniform) -> Result<()> {
34936        // UNIFORM(this, expression, [gen], [seed])
34937        self.write_keyword("UNIFORM");
34938        self.write("(");
34939        self.generate_expression(&e.this)?;
34940        self.write(", ");
34941        self.generate_expression(&e.expression)?;
34942        if let Some(gen) = &e.gen {
34943            self.write(", ");
34944            self.generate_expression(gen)?;
34945        }
34946        if let Some(seed) = &e.seed {
34947            self.write(", ");
34948            self.generate_expression(seed)?;
34949        }
34950        self.write(")");
34951        Ok(())
34952    }
34953
34954    fn generate_unique_column_constraint(&mut self, e: &UniqueColumnConstraint) -> Result<()> {
34955        // UNIQUE [NULLS NOT DISTINCT] [this] [index_type] [on_conflict] [options]
34956        self.write_keyword("UNIQUE");
34957        // Output NULLS NOT DISTINCT if nulls is set (PostgreSQL 15+ feature)
34958        if e.nulls.is_some() {
34959            self.write(" NULLS NOT DISTINCT");
34960        }
34961        if let Some(this) = &e.this {
34962            self.write_space();
34963            self.generate_expression(this)?;
34964        }
34965        if let Some(index_type) = &e.index_type {
34966            self.write(" USING ");
34967            self.generate_expression(index_type)?;
34968        }
34969        if let Some(on_conflict) = &e.on_conflict {
34970            self.write_space();
34971            self.generate_expression(on_conflict)?;
34972        }
34973        for opt in &e.options {
34974            self.write_space();
34975            self.generate_expression(opt)?;
34976        }
34977        Ok(())
34978    }
34979
34980    fn generate_unique_key_property(&mut self, e: &UniqueKeyProperty) -> Result<()> {
34981        // UNIQUE KEY (expressions)
34982        self.write_keyword("UNIQUE KEY");
34983        self.write(" (");
34984        for (i, expr) in e.expressions.iter().enumerate() {
34985            if i > 0 {
34986                self.write(", ");
34987            }
34988            self.generate_expression(expr)?;
34989        }
34990        self.write(")");
34991        Ok(())
34992    }
34993
34994    fn generate_rollup_property(&mut self, e: &RollupProperty) -> Result<()> {
34995        // ROLLUP (r1(col1, col2), r2(col1))
34996        self.write_keyword("ROLLUP");
34997        self.write(" (");
34998        for (i, index) in e.expressions.iter().enumerate() {
34999            if i > 0 {
35000                self.write(", ");
35001            }
35002            self.generate_identifier(&index.name)?;
35003            self.write("(");
35004            for (j, col) in index.expressions.iter().enumerate() {
35005                if j > 0 {
35006                    self.write(", ");
35007                }
35008                self.generate_identifier(col)?;
35009            }
35010            self.write(")");
35011        }
35012        self.write(")");
35013        Ok(())
35014    }
35015
35016    fn generate_unix_to_str(&mut self, e: &UnixToStr) -> Result<()> {
35017        match self.config.dialect {
35018            Some(DialectType::DuckDB) => {
35019                // DuckDB: STRFTIME(TO_TIMESTAMP(value), format)
35020                self.write_keyword("STRFTIME");
35021                self.write("(");
35022                self.write_keyword("TO_TIMESTAMP");
35023                self.write("(");
35024                self.generate_expression(&e.this)?;
35025                self.write("), '");
35026                if let Some(format) = &e.format {
35027                    self.write(format);
35028                }
35029                self.write("')");
35030            }
35031            Some(DialectType::Hive) => {
35032                // Hive: FROM_UNIXTIME(value, format) - elide format when it's the default
35033                self.write_keyword("FROM_UNIXTIME");
35034                self.write("(");
35035                self.generate_expression(&e.this)?;
35036                if let Some(format) = &e.format {
35037                    if format != "yyyy-MM-dd HH:mm:ss" {
35038                        self.write(", '");
35039                        self.write(format);
35040                        self.write("'");
35041                    }
35042                }
35043                self.write(")");
35044            }
35045            Some(DialectType::Presto) | Some(DialectType::Trino) => {
35046                // Presto: DATE_FORMAT(FROM_UNIXTIME(value), format)
35047                self.write_keyword("DATE_FORMAT");
35048                self.write("(");
35049                self.write_keyword("FROM_UNIXTIME");
35050                self.write("(");
35051                self.generate_expression(&e.this)?;
35052                self.write("), '");
35053                if let Some(format) = &e.format {
35054                    self.write(format);
35055                }
35056                self.write("')");
35057            }
35058            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
35059                // Spark: FROM_UNIXTIME(value, format)
35060                self.write_keyword("FROM_UNIXTIME");
35061                self.write("(");
35062                self.generate_expression(&e.this)?;
35063                if let Some(format) = &e.format {
35064                    self.write(", '");
35065                    self.write(format);
35066                    self.write("'");
35067                }
35068                self.write(")");
35069            }
35070            _ => {
35071                // Default: UNIX_TO_STR(this, [format])
35072                self.write_keyword("UNIX_TO_STR");
35073                self.write("(");
35074                self.generate_expression(&e.this)?;
35075                if let Some(format) = &e.format {
35076                    self.write(", '");
35077                    self.write(format);
35078                    self.write("'");
35079                }
35080                self.write(")");
35081            }
35082        }
35083        Ok(())
35084    }
35085
35086    fn generate_unix_to_time(&mut self, e: &UnixToTime) -> Result<()> {
35087        use crate::dialects::DialectType;
35088        let scale = e.scale.unwrap_or(0); // 0 = seconds
35089
35090        match self.config.dialect {
35091            Some(DialectType::Snowflake) => {
35092                // Snowflake: TO_TIMESTAMP(value[, scale]) - skip scale for seconds (0)
35093                self.write_keyword("TO_TIMESTAMP");
35094                self.write("(");
35095                self.generate_expression(&e.this)?;
35096                if let Some(s) = e.scale {
35097                    if s > 0 {
35098                        self.write(", ");
35099                        self.write(&s.to_string());
35100                    }
35101                }
35102                self.write(")");
35103            }
35104            Some(DialectType::BigQuery) => {
35105                // BigQuery: TIMESTAMP_SECONDS(value) / TIMESTAMP_MILLIS(value)
35106                // or TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64)) for other scales
35107                match scale {
35108                    0 => {
35109                        self.write_keyword("TIMESTAMP_SECONDS");
35110                        self.write("(");
35111                        self.generate_expression(&e.this)?;
35112                        self.write(")");
35113                    }
35114                    3 => {
35115                        self.write_keyword("TIMESTAMP_MILLIS");
35116                        self.write("(");
35117                        self.generate_expression(&e.this)?;
35118                        self.write(")");
35119                    }
35120                    6 => {
35121                        self.write_keyword("TIMESTAMP_MICROS");
35122                        self.write("(");
35123                        self.generate_expression(&e.this)?;
35124                        self.write(")");
35125                    }
35126                    _ => {
35127                        // TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64))
35128                        self.write_keyword("TIMESTAMP_SECONDS");
35129                        self.write("(CAST(");
35130                        self.generate_expression(&e.this)?;
35131                        self.write(&format!(" / POWER(10, {}) AS INT64))", scale));
35132                    }
35133                }
35134            }
35135            Some(DialectType::Spark) => {
35136                // Spark: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
35137                // TIMESTAMP_MILLIS(value) for scale=3
35138                // TIMESTAMP_MICROS(value) for scale=6
35139                // TIMESTAMP_SECONDS(value / POWER(10, scale)) for other scales
35140                match scale {
35141                    0 => {
35142                        self.write_keyword("CAST");
35143                        self.write("(");
35144                        self.write_keyword("FROM_UNIXTIME");
35145                        self.write("(");
35146                        self.generate_expression(&e.this)?;
35147                        self.write(") ");
35148                        self.write_keyword("AS TIMESTAMP");
35149                        self.write(")");
35150                    }
35151                    3 => {
35152                        self.write_keyword("TIMESTAMP_MILLIS");
35153                        self.write("(");
35154                        self.generate_expression(&e.this)?;
35155                        self.write(")");
35156                    }
35157                    6 => {
35158                        self.write_keyword("TIMESTAMP_MICROS");
35159                        self.write("(");
35160                        self.generate_expression(&e.this)?;
35161                        self.write(")");
35162                    }
35163                    _ => {
35164                        self.write_keyword("TIMESTAMP_SECONDS");
35165                        self.write("(");
35166                        self.generate_expression(&e.this)?;
35167                        self.write(&format!(" / POWER(10, {}))", scale));
35168                    }
35169                }
35170            }
35171            Some(DialectType::Databricks) => {
35172                // Databricks: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
35173                // TIMESTAMP_MILLIS(value) for scale=3
35174                // TIMESTAMP_MICROS(value) for scale=6
35175                match scale {
35176                    0 => {
35177                        self.write_keyword("CAST");
35178                        self.write("(");
35179                        self.write_keyword("FROM_UNIXTIME");
35180                        self.write("(");
35181                        self.generate_expression(&e.this)?;
35182                        self.write(") ");
35183                        self.write_keyword("AS TIMESTAMP");
35184                        self.write(")");
35185                    }
35186                    3 => {
35187                        self.write_keyword("TIMESTAMP_MILLIS");
35188                        self.write("(");
35189                        self.generate_expression(&e.this)?;
35190                        self.write(")");
35191                    }
35192                    6 => {
35193                        self.write_keyword("TIMESTAMP_MICROS");
35194                        self.write("(");
35195                        self.generate_expression(&e.this)?;
35196                        self.write(")");
35197                    }
35198                    _ => {
35199                        self.write_keyword("TIMESTAMP_SECONDS");
35200                        self.write("(");
35201                        self.generate_expression(&e.this)?;
35202                        self.write(&format!(" / POWER(10, {}))", scale));
35203                    }
35204                }
35205            }
35206            Some(DialectType::Hive) => {
35207                // Hive: FROM_UNIXTIME(value)
35208                if scale == 0 {
35209                    self.write_keyword("FROM_UNIXTIME");
35210                    self.write("(");
35211                    self.generate_expression(&e.this)?;
35212                    self.write(")");
35213                } else {
35214                    self.write_keyword("FROM_UNIXTIME");
35215                    self.write("(");
35216                    self.generate_expression(&e.this)?;
35217                    self.write(&format!(" / POWER(10, {})", scale));
35218                    self.write(")");
35219                }
35220            }
35221            Some(DialectType::Presto) | Some(DialectType::Trino) => {
35222                // Presto: FROM_UNIXTIME(CAST(value AS DOUBLE) / POW(10, scale)) for scale > 0
35223                // FROM_UNIXTIME(value) for scale=0
35224                if scale == 0 {
35225                    self.write_keyword("FROM_UNIXTIME");
35226                    self.write("(");
35227                    self.generate_expression(&e.this)?;
35228                    self.write(")");
35229                } else {
35230                    self.write_keyword("FROM_UNIXTIME");
35231                    self.write("(CAST(");
35232                    self.generate_expression(&e.this)?;
35233                    self.write(&format!(" AS DOUBLE) / POW(10, {}))", scale));
35234                }
35235            }
35236            Some(DialectType::DuckDB) => {
35237                // DuckDB: TO_TIMESTAMP(value) for scale=0
35238                // EPOCH_MS(value) for scale=3
35239                // MAKE_TIMESTAMP(value) for scale=6
35240                match scale {
35241                    0 => {
35242                        self.write_keyword("TO_TIMESTAMP");
35243                        self.write("(");
35244                        self.generate_expression(&e.this)?;
35245                        self.write(")");
35246                    }
35247                    3 => {
35248                        self.write_keyword("EPOCH_MS");
35249                        self.write("(");
35250                        self.generate_expression(&e.this)?;
35251                        self.write(")");
35252                    }
35253                    6 => {
35254                        self.write_keyword("MAKE_TIMESTAMP");
35255                        self.write("(");
35256                        self.generate_expression(&e.this)?;
35257                        self.write(")");
35258                    }
35259                    _ => {
35260                        self.write_keyword("TO_TIMESTAMP");
35261                        self.write("(");
35262                        self.generate_expression(&e.this)?;
35263                        self.write(&format!(" / POWER(10, {}))", scale));
35264                        self.write_keyword(" AT TIME ZONE");
35265                        self.write(" 'UTC'");
35266                    }
35267                }
35268            }
35269            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
35270                // Doris/StarRocks: FROM_UNIXTIME(value)
35271                self.write_keyword("FROM_UNIXTIME");
35272                self.write("(");
35273                self.generate_expression(&e.this)?;
35274                self.write(")");
35275            }
35276            Some(DialectType::Oracle) => {
35277                // Oracle: TO_DATE('1970-01-01', 'YYYY-MM-DD') + (x / 86400)
35278                self.write("TO_DATE('1970-01-01', 'YYYY-MM-DD') + (");
35279                self.generate_expression(&e.this)?;
35280                self.write(" / 86400)");
35281            }
35282            Some(DialectType::Redshift) => {
35283                // Redshift: (TIMESTAMP 'epoch' + value * INTERVAL '1 SECOND') for scale=0
35284                // (TIMESTAMP 'epoch' + (value / POWER(10, scale)) * INTERVAL '1 SECOND') for scale > 0
35285                self.write("(TIMESTAMP 'epoch' + ");
35286                if scale == 0 {
35287                    self.generate_expression(&e.this)?;
35288                } else {
35289                    self.write("(");
35290                    self.generate_expression(&e.this)?;
35291                    self.write(&format!(" / POWER(10, {}))", scale));
35292                }
35293                self.write(" * INTERVAL '1 SECOND')");
35294            }
35295            _ => {
35296                // Default: TO_TIMESTAMP(value[, scale])
35297                self.write_keyword("TO_TIMESTAMP");
35298                self.write("(");
35299                self.generate_expression(&e.this)?;
35300                if let Some(s) = e.scale {
35301                    self.write(", ");
35302                    self.write(&s.to_string());
35303                }
35304                self.write(")");
35305            }
35306        }
35307        Ok(())
35308    }
35309
35310    fn generate_unpivot_columns(&mut self, e: &UnpivotColumns) -> Result<()> {
35311        // NAME col VALUE col1, col2, ...
35312        if !matches!(&*e.this, Expression::Null(_)) {
35313            self.write_keyword("NAME");
35314            self.write_space();
35315            self.generate_expression(&e.this)?;
35316        }
35317        if !e.expressions.is_empty() {
35318            self.write_space();
35319            self.write_keyword("VALUE");
35320            self.write_space();
35321            for (i, expr) in e.expressions.iter().enumerate() {
35322                if i > 0 {
35323                    self.write(", ");
35324                }
35325                self.generate_expression(expr)?;
35326            }
35327        }
35328        Ok(())
35329    }
35330
35331    fn generate_user_defined_function(&mut self, e: &UserDefinedFunction) -> Result<()> {
35332        // this(expressions) or (this)(expressions)
35333        if e.wrapped.is_some() {
35334            self.write("(");
35335        }
35336        self.generate_expression(&e.this)?;
35337        if e.wrapped.is_some() {
35338            self.write(")");
35339        }
35340        self.write("(");
35341        for (i, expr) in e.expressions.iter().enumerate() {
35342            if i > 0 {
35343                self.write(", ");
35344            }
35345            self.generate_expression(expr)?;
35346        }
35347        self.write(")");
35348        Ok(())
35349    }
35350
35351    fn generate_using_template_property(&mut self, e: &UsingTemplateProperty) -> Result<()> {
35352        // USING TEMPLATE this
35353        self.write_keyword("USING TEMPLATE");
35354        self.write_space();
35355        self.generate_expression(&e.this)?;
35356        Ok(())
35357    }
35358
35359    fn generate_utc_time(&mut self, _e: &UtcTime) -> Result<()> {
35360        // UTC_TIME
35361        self.write_keyword("UTC_TIME");
35362        Ok(())
35363    }
35364
35365    fn generate_utc_timestamp(&mut self, _e: &UtcTimestamp) -> Result<()> {
35366        // UTC_TIMESTAMP
35367        self.write_keyword("UTC_TIMESTAMP");
35368        Ok(())
35369    }
35370
35371    fn generate_uuid(&mut self, e: &Uuid) -> Result<()> {
35372        use crate::dialects::DialectType;
35373        // Choose UUID function name based on target dialect
35374        let func_name = match self.config.dialect {
35375            Some(DialectType::Snowflake) => "UUID_STRING",
35376            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
35377            Some(DialectType::BigQuery) => "GENERATE_UUID",
35378            _ => {
35379                if let Some(name) = &e.name {
35380                    name.as_str()
35381                } else {
35382                    "UUID"
35383                }
35384            }
35385        };
35386        self.write_keyword(func_name);
35387        self.write("(");
35388        if let Some(this) = &e.this {
35389            self.generate_expression(this)?;
35390        }
35391        self.write(")");
35392        Ok(())
35393    }
35394
35395    fn generate_var_map(&mut self, e: &VarMap) -> Result<()> {
35396        // MAP(key1, value1, key2, value2, ...)
35397        self.write_keyword("MAP");
35398        self.write("(");
35399        let mut first = true;
35400        for (k, v) in e.keys.iter().zip(e.values.iter()) {
35401            if !first {
35402                self.write(", ");
35403            }
35404            self.generate_expression(k)?;
35405            self.write(", ");
35406            self.generate_expression(v)?;
35407            first = false;
35408        }
35409        self.write(")");
35410        Ok(())
35411    }
35412
35413    fn generate_vector_search(&mut self, e: &VectorSearch) -> Result<()> {
35414        // VECTOR_SEARCH(this, column_to_search, query_table, query_column_to_search, top_k, distance_type, ...)
35415        self.write_keyword("VECTOR_SEARCH");
35416        self.write("(");
35417        self.generate_expression(&e.this)?;
35418        if let Some(col) = &e.column_to_search {
35419            self.write(", ");
35420            self.generate_expression(col)?;
35421        }
35422        if let Some(query_table) = &e.query_table {
35423            self.write(", ");
35424            self.generate_expression(query_table)?;
35425        }
35426        if let Some(query_col) = &e.query_column_to_search {
35427            self.write(", ");
35428            self.generate_expression(query_col)?;
35429        }
35430        if let Some(top_k) = &e.top_k {
35431            self.write(", ");
35432            self.generate_expression(top_k)?;
35433        }
35434        if let Some(dist_type) = &e.distance_type {
35435            self.write(", ");
35436            self.generate_expression(dist_type)?;
35437        }
35438        self.write(")");
35439        Ok(())
35440    }
35441
35442    fn generate_version(&mut self, e: &Version) -> Result<()> {
35443        // Python: f"FOR {expression.name} {kind} {expr}"
35444        // e.this = Identifier("TIMESTAMP" or "VERSION")
35445        // e.kind = "AS OF" (or "BETWEEN", etc.)
35446        // e.expression = the value expression
35447        // Hive does NOT use the FOR prefix for time travel
35448        use crate::dialects::DialectType;
35449        let skip_for = matches!(
35450            self.config.dialect,
35451            Some(DialectType::Hive) | Some(DialectType::Spark)
35452        );
35453        if !skip_for {
35454            self.write_keyword("FOR");
35455            self.write_space();
35456        }
35457        // Extract the name from this (which is an Identifier expression)
35458        match e.this.as_ref() {
35459            Expression::Identifier(ident) => {
35460                self.write_keyword(&ident.name);
35461            }
35462            _ => {
35463                self.generate_expression(&e.this)?;
35464            }
35465        }
35466        self.write_space();
35467        self.write_keyword(&e.kind);
35468        if let Some(expression) = &e.expression {
35469            self.write_space();
35470            self.generate_expression(expression)?;
35471        }
35472        Ok(())
35473    }
35474
35475    fn generate_view_attribute_property(&mut self, e: &ViewAttributeProperty) -> Result<()> {
35476        // Python: return self.sql(expression, "this")
35477        self.generate_expression(&e.this)?;
35478        Ok(())
35479    }
35480
35481    fn generate_volatile_property(&mut self, e: &VolatileProperty) -> Result<()> {
35482        // Python: return "VOLATILE" if expression.args.get("this") is None else "NOT VOLATILE"
35483        if e.this.is_some() {
35484            self.write_keyword("NOT VOLATILE");
35485        } else {
35486            self.write_keyword("VOLATILE");
35487        }
35488        Ok(())
35489    }
35490
35491    fn generate_watermark_column_constraint(
35492        &mut self,
35493        e: &WatermarkColumnConstraint,
35494    ) -> Result<()> {
35495        // Python: f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
35496        self.write_keyword("WATERMARK FOR");
35497        self.write_space();
35498        self.generate_expression(&e.this)?;
35499        self.write_space();
35500        self.write_keyword("AS");
35501        self.write_space();
35502        self.generate_expression(&e.expression)?;
35503        Ok(())
35504    }
35505
35506    fn generate_week(&mut self, e: &Week) -> Result<()> {
35507        // Python: return self.func("WEEK", expression.this, expression.args.get("mode"))
35508        self.write_keyword("WEEK");
35509        self.write("(");
35510        self.generate_expression(&e.this)?;
35511        if let Some(mode) = &e.mode {
35512            self.write(", ");
35513            self.generate_expression(mode)?;
35514        }
35515        self.write(")");
35516        Ok(())
35517    }
35518
35519    fn generate_when(&mut self, e: &When) -> Result<()> {
35520        // Python: WHEN {matched}{source}{condition} THEN {then}
35521        // matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
35522        // source = " BY SOURCE" if MATCHED_BY_SOURCE and expression.args.get("source") else ""
35523        self.write_keyword("WHEN");
35524        self.write_space();
35525
35526        // Check if matched
35527        if let Some(matched) = &e.matched {
35528            // Check the expression - if it's a boolean true, use MATCHED, otherwise NOT MATCHED
35529            match matched.as_ref() {
35530                Expression::Boolean(b) if b.value => {
35531                    self.write_keyword("MATCHED");
35532                }
35533                _ => {
35534                    self.write_keyword("NOT MATCHED");
35535                }
35536            }
35537        } else {
35538            self.write_keyword("NOT MATCHED");
35539        }
35540
35541        // BY SOURCE / BY TARGET
35542        // source = Boolean(true) means BY SOURCE, Boolean(false) means BY TARGET
35543        // BY TARGET is the default and typically omitted in output
35544        // Only emit if the dialect supports BY SOURCE syntax
35545        if self.config.matched_by_source {
35546            if let Some(source) = &e.source {
35547                if let Expression::Boolean(b) = source.as_ref() {
35548                    if b.value {
35549                        // BY SOURCE
35550                        self.write_space();
35551                        self.write_keyword("BY SOURCE");
35552                    }
35553                    // BY TARGET (b.value == false) is omitted as it's the default
35554                } else {
35555                    // For non-boolean source, output as BY SOURCE (legacy behavior)
35556                    self.write_space();
35557                    self.write_keyword("BY SOURCE");
35558                }
35559            }
35560        }
35561
35562        // Condition
35563        if let Some(condition) = &e.condition {
35564            self.write_space();
35565            self.write_keyword("AND");
35566            self.write_space();
35567            self.generate_expression(condition)?;
35568        }
35569
35570        self.write_space();
35571        self.write_keyword("THEN");
35572        self.write_space();
35573
35574        // Generate the then expression (could be INSERT, UPDATE, DELETE)
35575        // MERGE actions are stored as Tuples with the action keyword as first element
35576        self.generate_merge_action(&e.then)?;
35577
35578        Ok(())
35579    }
35580
35581    fn generate_merge_action(&mut self, action: &Expression) -> Result<()> {
35582        match action {
35583            Expression::Tuple(tuple) => {
35584                let elements = &tuple.expressions;
35585                if elements.is_empty() {
35586                    return self.generate_expression(action);
35587                }
35588                // Check if first element is a Var (INSERT, UPDATE, DELETE, etc.)
35589                match &elements[0] {
35590                    Expression::Var(v) if v.this == "INSERT" => {
35591                        self.write_keyword("INSERT");
35592                        // Spark: INSERT * (insert all columns)
35593                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
35594                            self.write(" *");
35595                        } else {
35596                            let mut values_idx = 1;
35597                            // Check if second element is column list (Tuple)
35598                            if elements.len() > 1 {
35599                                if let Expression::Tuple(cols) = &elements[1] {
35600                                    // Could be columns or values - if there's a third element, second is columns
35601                                    if elements.len() > 2 {
35602                                        // Second is columns, third is values
35603                                        self.write(" (");
35604                                        for (i, col) in cols.expressions.iter().enumerate() {
35605                                            if i > 0 {
35606                                                self.write(", ");
35607                                            }
35608                                            // Strip MERGE target qualifiers from INSERT column list
35609                                            if !self.merge_strip_qualifiers.is_empty() {
35610                                                let stripped = self.strip_merge_qualifier(col);
35611                                                self.generate_expression(&stripped)?;
35612                                            } else {
35613                                                self.generate_expression(col)?;
35614                                            }
35615                                        }
35616                                        self.write(")");
35617                                        values_idx = 2;
35618                                    } else {
35619                                        // Only two elements: INSERT + values (no explicit columns)
35620                                        values_idx = 1;
35621                                    }
35622                                }
35623                            }
35624                            // Generate VALUES clause
35625                            if values_idx < elements.len() {
35626                                // Check if it's INSERT ROW (BigQuery) — no VALUES keyword needed
35627                                let is_row = matches!(&elements[values_idx], Expression::Var(v) if v.this == "ROW");
35628                                if !is_row {
35629                                    self.write_space();
35630                                    self.write_keyword("VALUES");
35631                                }
35632                                self.write(" ");
35633                                if let Expression::Tuple(vals) = &elements[values_idx] {
35634                                    self.write("(");
35635                                    for (i, val) in vals.expressions.iter().enumerate() {
35636                                        if i > 0 {
35637                                            self.write(", ");
35638                                        }
35639                                        self.generate_expression(val)?;
35640                                    }
35641                                    self.write(")");
35642                                } else {
35643                                    self.generate_expression(&elements[values_idx])?;
35644                                }
35645                            }
35646                        } // close else for INSERT * check
35647                    }
35648                    Expression::Var(v) if v.this == "UPDATE" => {
35649                        self.write_keyword("UPDATE");
35650                        // Spark: UPDATE * (update all columns)
35651                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
35652                            self.write(" *");
35653                        } else if elements.len() > 1 {
35654                            self.write_space();
35655                            self.write_keyword("SET");
35656                            // In pretty mode, put assignments on next line with extra indent
35657                            if self.config.pretty {
35658                                self.write_newline();
35659                                self.indent_level += 1;
35660                                self.write_indent();
35661                            } else {
35662                                self.write_space();
35663                            }
35664                            if let Expression::Tuple(assignments) = &elements[1] {
35665                                for (i, assignment) in assignments.expressions.iter().enumerate() {
35666                                    if i > 0 {
35667                                        if self.config.pretty {
35668                                            self.write(",");
35669                                            self.write_newline();
35670                                            self.write_indent();
35671                                        } else {
35672                                            self.write(", ");
35673                                        }
35674                                    }
35675                                    // Strip MERGE target qualifiers from left side of UPDATE SET
35676                                    if !self.merge_strip_qualifiers.is_empty() {
35677                                        self.generate_merge_set_assignment(assignment)?;
35678                                    } else {
35679                                        self.generate_expression(assignment)?;
35680                                    }
35681                                }
35682                            } else {
35683                                self.generate_expression(&elements[1])?;
35684                            }
35685                            if self.config.pretty {
35686                                self.indent_level -= 1;
35687                            }
35688                        }
35689                    }
35690                    _ => {
35691                        // Fallback: generic tuple generation
35692                        self.generate_expression(action)?;
35693                    }
35694                }
35695            }
35696            Expression::Var(v)
35697                if v.this == "INSERT"
35698                    || v.this == "UPDATE"
35699                    || v.this == "DELETE"
35700                    || v.this == "DO NOTHING" =>
35701            {
35702                self.write_keyword(&v.this);
35703            }
35704            _ => {
35705                self.generate_expression(action)?;
35706            }
35707        }
35708        Ok(())
35709    }
35710
35711    /// Generate a MERGE UPDATE SET assignment, stripping target table qualifier from left side
35712    fn generate_merge_set_assignment(&mut self, assignment: &Expression) -> Result<()> {
35713        match assignment {
35714            Expression::Eq(eq) => {
35715                // Strip qualifier from the left side if it matches a MERGE target name
35716                let stripped_left = self.strip_merge_qualifier(&eq.left);
35717                self.generate_expression(&stripped_left)?;
35718                self.write(" = ");
35719                self.generate_expression(&eq.right)?;
35720                Ok(())
35721            }
35722            other => self.generate_expression(other),
35723        }
35724    }
35725
35726    /// Strip table qualifier from a column reference if it matches a MERGE target name
35727    fn strip_merge_qualifier(&self, expr: &Expression) -> Expression {
35728        match expr {
35729            Expression::Column(col) => {
35730                if let Some(ref table_ident) = col.table {
35731                    if self
35732                        .merge_strip_qualifiers
35733                        .iter()
35734                        .any(|n| n.eq_ignore_ascii_case(&table_ident.name))
35735                    {
35736                        // Strip the table qualifier
35737                        let mut col = col.clone();
35738                        col.table = None;
35739                        return Expression::Column(col);
35740                    }
35741                }
35742                expr.clone()
35743            }
35744            Expression::Dot(dot) => {
35745                // table.column -> column (strip qualifier)
35746                if let Expression::Identifier(id) = &dot.this {
35747                    if self
35748                        .merge_strip_qualifiers
35749                        .iter()
35750                        .any(|n| n.eq_ignore_ascii_case(&id.name))
35751                    {
35752                        return Expression::Identifier(dot.field.clone());
35753                    }
35754                }
35755                expr.clone()
35756            }
35757            _ => expr.clone(),
35758        }
35759    }
35760
35761    fn generate_whens(&mut self, e: &Whens) -> Result<()> {
35762        // Python: return self.expressions(expression, sep=" ", indent=False)
35763        for (i, expr) in e.expressions.iter().enumerate() {
35764            if i > 0 {
35765                // In pretty mode, each WHEN clause on its own line
35766                if self.config.pretty {
35767                    self.write_newline();
35768                    self.write_indent();
35769                } else {
35770                    self.write_space();
35771                }
35772            }
35773            self.generate_expression(expr)?;
35774        }
35775        Ok(())
35776    }
35777
35778    fn generate_where(&mut self, e: &Where) -> Result<()> {
35779        // Python: return f"{self.seg('WHERE')}{self.sep()}{this}"
35780        self.write_keyword("WHERE");
35781        self.write_space();
35782        self.generate_expression(&e.this)?;
35783        Ok(())
35784    }
35785
35786    fn generate_width_bucket(&mut self, e: &WidthBucket) -> Result<()> {
35787        // Python: return self.func("WIDTH_BUCKET", expression.this, ...)
35788        self.write_keyword("WIDTH_BUCKET");
35789        self.write("(");
35790        self.generate_expression(&e.this)?;
35791        if let Some(min_value) = &e.min_value {
35792            self.write(", ");
35793            self.generate_expression(min_value)?;
35794        }
35795        if let Some(max_value) = &e.max_value {
35796            self.write(", ");
35797            self.generate_expression(max_value)?;
35798        }
35799        if let Some(num_buckets) = &e.num_buckets {
35800            self.write(", ");
35801            self.generate_expression(num_buckets)?;
35802        }
35803        self.write(")");
35804        Ok(())
35805    }
35806
35807    fn generate_window(&mut self, e: &WindowSpec) -> Result<()> {
35808        // Window specification: PARTITION BY ... ORDER BY ... frame
35809        self.generate_window_spec(e)
35810    }
35811
35812    fn generate_window_spec(&mut self, e: &WindowSpec) -> Result<()> {
35813        // Window specification: PARTITION BY ... ORDER BY ... frame
35814        let mut has_content = false;
35815
35816        // PARTITION BY
35817        if !e.partition_by.is_empty() {
35818            self.write_keyword("PARTITION BY");
35819            self.write_space();
35820            for (i, expr) in e.partition_by.iter().enumerate() {
35821                if i > 0 {
35822                    self.write(", ");
35823                }
35824                self.generate_expression(expr)?;
35825            }
35826            has_content = true;
35827        }
35828
35829        // ORDER BY
35830        if !e.order_by.is_empty() {
35831            if has_content {
35832                self.write_space();
35833            }
35834            self.write_keyword("ORDER BY");
35835            self.write_space();
35836            for (i, ordered) in e.order_by.iter().enumerate() {
35837                if i > 0 {
35838                    self.write(", ");
35839                }
35840                self.generate_expression(&ordered.this)?;
35841                if ordered.desc {
35842                    self.write_space();
35843                    self.write_keyword("DESC");
35844                } else if ordered.explicit_asc {
35845                    self.write_space();
35846                    self.write_keyword("ASC");
35847                }
35848                if let Some(nulls_first) = ordered.nulls_first {
35849                    self.write_space();
35850                    self.write_keyword("NULLS");
35851                    self.write_space();
35852                    if nulls_first {
35853                        self.write_keyword("FIRST");
35854                    } else {
35855                        self.write_keyword("LAST");
35856                    }
35857                }
35858            }
35859            has_content = true;
35860        }
35861
35862        // Frame specification
35863        if let Some(frame) = &e.frame {
35864            if has_content {
35865                self.write_space();
35866            }
35867            self.generate_window_frame(frame)?;
35868        }
35869
35870        Ok(())
35871    }
35872
35873    fn generate_with_data_property(&mut self, e: &WithDataProperty) -> Result<()> {
35874        // Python: f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
35875        self.write_keyword("WITH");
35876        self.write_space();
35877        if e.no.is_some() {
35878            self.write_keyword("NO");
35879            self.write_space();
35880        }
35881        self.write_keyword("DATA");
35882
35883        // statistics
35884        if let Some(statistics) = &e.statistics {
35885            self.write_space();
35886            self.write_keyword("AND");
35887            self.write_space();
35888            // Check if statistics is true or false
35889            match statistics.as_ref() {
35890                Expression::Boolean(b) if !b.value => {
35891                    self.write_keyword("NO");
35892                    self.write_space();
35893                }
35894                _ => {}
35895            }
35896            self.write_keyword("STATISTICS");
35897        }
35898        Ok(())
35899    }
35900
35901    fn generate_with_fill(&mut self, e: &WithFill) -> Result<()> {
35902        // Python: f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
35903        self.write_keyword("WITH FILL");
35904
35905        if let Some(from_) = &e.from_ {
35906            self.write_space();
35907            self.write_keyword("FROM");
35908            self.write_space();
35909            self.generate_expression(from_)?;
35910        }
35911
35912        if let Some(to) = &e.to {
35913            self.write_space();
35914            self.write_keyword("TO");
35915            self.write_space();
35916            self.generate_expression(to)?;
35917        }
35918
35919        if let Some(step) = &e.step {
35920            self.write_space();
35921            self.write_keyword("STEP");
35922            self.write_space();
35923            self.generate_expression(step)?;
35924        }
35925
35926        if let Some(staleness) = &e.staleness {
35927            self.write_space();
35928            self.write_keyword("STALENESS");
35929            self.write_space();
35930            self.generate_expression(staleness)?;
35931        }
35932
35933        if let Some(interpolate) = &e.interpolate {
35934            self.write_space();
35935            self.write_keyword("INTERPOLATE");
35936            self.write(" (");
35937            // INTERPOLATE items use reversed alias format: name AS expression
35938            self.generate_interpolate_item(interpolate)?;
35939            self.write(")");
35940        }
35941
35942        Ok(())
35943    }
35944
35945    /// Generate INTERPOLATE items with reversed alias format (name AS expression)
35946    fn generate_interpolate_item(&mut self, expr: &Expression) -> Result<()> {
35947        match expr {
35948            Expression::Alias(alias) => {
35949                // Output as: alias_name AS expression
35950                self.generate_identifier(&alias.alias)?;
35951                self.write_space();
35952                self.write_keyword("AS");
35953                self.write_space();
35954                self.generate_expression(&alias.this)?;
35955            }
35956            Expression::Tuple(tuple) => {
35957                for (i, item) in tuple.expressions.iter().enumerate() {
35958                    if i > 0 {
35959                        self.write(", ");
35960                    }
35961                    self.generate_interpolate_item(item)?;
35962                }
35963            }
35964            other => {
35965                self.generate_expression(other)?;
35966            }
35967        }
35968        Ok(())
35969    }
35970
35971    fn generate_with_journal_table_property(&mut self, e: &WithJournalTableProperty) -> Result<()> {
35972        // Python: return f"WITH JOURNAL TABLE={self.sql(expression, 'this')}"
35973        self.write_keyword("WITH JOURNAL TABLE");
35974        self.write("=");
35975        self.generate_expression(&e.this)?;
35976        Ok(())
35977    }
35978
35979    fn generate_with_operator(&mut self, e: &WithOperator) -> Result<()> {
35980        // Python: return f"{self.sql(expression, 'this')} WITH {self.sql(expression, 'op')}"
35981        self.generate_expression(&e.this)?;
35982        self.write_space();
35983        self.write_keyword("WITH");
35984        self.write_space();
35985        self.write_keyword(&e.op);
35986        Ok(())
35987    }
35988
35989    fn generate_with_procedure_options(&mut self, e: &WithProcedureOptions) -> Result<()> {
35990        // Python: return f"WITH {self.expressions(expression, flat=True)}"
35991        self.write_keyword("WITH");
35992        self.write_space();
35993        for (i, expr) in e.expressions.iter().enumerate() {
35994            if i > 0 {
35995                self.write(", ");
35996            }
35997            self.generate_expression(expr)?;
35998        }
35999        Ok(())
36000    }
36001
36002    fn generate_with_schema_binding_property(
36003        &mut self,
36004        e: &WithSchemaBindingProperty,
36005    ) -> Result<()> {
36006        // Python: return f"WITH {self.sql(expression, 'this')}"
36007        self.write_keyword("WITH");
36008        self.write_space();
36009        self.generate_expression(&e.this)?;
36010        Ok(())
36011    }
36012
36013    fn generate_with_system_versioning_property(
36014        &mut self,
36015        e: &WithSystemVersioningProperty,
36016    ) -> Result<()> {
36017        // Python: complex logic for SYSTEM_VERSIONING with options
36018        // SYSTEM_VERSIONING=ON(HISTORY_TABLE=..., DATA_CONSISTENCY_CHECK=..., HISTORY_RETENTION_PERIOD=...)
36019        // or SYSTEM_VERSIONING=ON/OFF
36020        // with WITH(...) wrapper if with_ is set
36021
36022        let mut parts = Vec::new();
36023
36024        if let Some(this) = &e.this {
36025            // HISTORY_TABLE=...
36026            let mut s = String::from("HISTORY_TABLE=");
36027            let mut gen = Generator::new();
36028            gen.generate_expression(this)?;
36029            s.push_str(&gen.output);
36030            parts.push(s);
36031        }
36032
36033        if let Some(data_consistency) = &e.data_consistency {
36034            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
36035            let mut gen = Generator::new();
36036            gen.generate_expression(data_consistency)?;
36037            s.push_str(&gen.output);
36038            parts.push(s);
36039        }
36040
36041        if let Some(retention_period) = &e.retention_period {
36042            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
36043            let mut gen = Generator::new();
36044            gen.generate_expression(retention_period)?;
36045            s.push_str(&gen.output);
36046            parts.push(s);
36047        }
36048
36049        self.write_keyword("SYSTEM_VERSIONING");
36050        self.write("=");
36051
36052        if !parts.is_empty() {
36053            self.write_keyword("ON");
36054            self.write("(");
36055            self.write(&parts.join(", "));
36056            self.write(")");
36057        } else if e.on.is_some() {
36058            self.write_keyword("ON");
36059        } else {
36060            self.write_keyword("OFF");
36061        }
36062
36063        // Wrap in WITH(...) if with_ is set
36064        if e.with_.is_some() {
36065            let inner = self.output.clone();
36066            self.output.clear();
36067            self.write("WITH(");
36068            self.write(&inner);
36069            self.write(")");
36070        }
36071
36072        Ok(())
36073    }
36074
36075    fn generate_with_table_hint(&mut self, e: &WithTableHint) -> Result<()> {
36076        // Python: f"WITH ({self.expressions(expression, flat=True)})"
36077        self.write_keyword("WITH");
36078        self.write(" (");
36079        for (i, expr) in e.expressions.iter().enumerate() {
36080            if i > 0 {
36081                self.write(", ");
36082            }
36083            self.generate_expression(expr)?;
36084        }
36085        self.write(")");
36086        Ok(())
36087    }
36088
36089    fn generate_xml_element(&mut self, e: &XMLElement) -> Result<()> {
36090        // Python: prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
36091        // return self.func("XMLELEMENT", name, *expression.expressions)
36092        self.write_keyword("XMLELEMENT");
36093        self.write("(");
36094
36095        if e.evalname.is_some() {
36096            self.write_keyword("EVALNAME");
36097        } else {
36098            self.write_keyword("NAME");
36099        }
36100        self.write_space();
36101        self.generate_expression(&e.this)?;
36102
36103        for expr in &e.expressions {
36104            self.write(", ");
36105            self.generate_expression(expr)?;
36106        }
36107        self.write(")");
36108        Ok(())
36109    }
36110
36111    fn generate_xml_get(&mut self, e: &XMLGet) -> Result<()> {
36112        // XMLGET(this, expression [, instance])
36113        self.write_keyword("XMLGET");
36114        self.write("(");
36115        self.generate_expression(&e.this)?;
36116        self.write(", ");
36117        self.generate_expression(&e.expression)?;
36118        if let Some(instance) = &e.instance {
36119            self.write(", ");
36120            self.generate_expression(instance)?;
36121        }
36122        self.write(")");
36123        Ok(())
36124    }
36125
36126    fn generate_xml_key_value_option(&mut self, e: &XMLKeyValueOption) -> Result<()> {
36127        // Python: this + optional (expr)
36128        self.generate_expression(&e.this)?;
36129        if let Some(expression) = &e.expression {
36130            self.write("(");
36131            self.generate_expression(expression)?;
36132            self.write(")");
36133        }
36134        Ok(())
36135    }
36136
36137    fn generate_xml_table(&mut self, e: &XMLTable) -> Result<()> {
36138        // Python: XMLTABLE(namespaces + this + passing + by_ref + columns)
36139        self.write_keyword("XMLTABLE");
36140        self.write("(");
36141
36142        if self.config.pretty {
36143            self.indent_level += 1;
36144            self.write_newline();
36145            self.write_indent();
36146            self.generate_expression(&e.this)?;
36147
36148            if let Some(passing) = &e.passing {
36149                self.write_newline();
36150                self.write_indent();
36151                self.write_keyword("PASSING");
36152                if let Expression::Tuple(tuple) = passing.as_ref() {
36153                    for expr in &tuple.expressions {
36154                        self.write_newline();
36155                        self.indent_level += 1;
36156                        self.write_indent();
36157                        self.generate_expression(expr)?;
36158                        self.indent_level -= 1;
36159                    }
36160                } else {
36161                    self.write_newline();
36162                    self.indent_level += 1;
36163                    self.write_indent();
36164                    self.generate_expression(passing)?;
36165                    self.indent_level -= 1;
36166                }
36167            }
36168
36169            if e.by_ref.is_some() {
36170                self.write_newline();
36171                self.write_indent();
36172                self.write_keyword("RETURNING SEQUENCE BY REF");
36173            }
36174
36175            if !e.columns.is_empty() {
36176                self.write_newline();
36177                self.write_indent();
36178                self.write_keyword("COLUMNS");
36179                for (i, col) in e.columns.iter().enumerate() {
36180                    self.write_newline();
36181                    self.indent_level += 1;
36182                    self.write_indent();
36183                    self.generate_expression(col)?;
36184                    self.indent_level -= 1;
36185                    if i < e.columns.len() - 1 {
36186                        self.write(",");
36187                    }
36188                }
36189            }
36190
36191            self.indent_level -= 1;
36192            self.write_newline();
36193            self.write_indent();
36194            self.write(")");
36195            return Ok(());
36196        }
36197
36198        // Namespaces - unwrap Tuple to generate comma-separated list without parentheses
36199        if let Some(namespaces) = &e.namespaces {
36200            self.write_keyword("XMLNAMESPACES");
36201            self.write("(");
36202            // Unwrap Tuple if present to avoid extra parentheses
36203            if let Expression::Tuple(tuple) = namespaces.as_ref() {
36204                for (i, expr) in tuple.expressions.iter().enumerate() {
36205                    if i > 0 {
36206                        self.write(", ");
36207                    }
36208                    // Python pattern: if it's an Alias, output as-is; otherwise prepend DEFAULT
36209                    // See xmlnamespace_sql in generator.py
36210                    if !matches!(expr, Expression::Alias(_)) {
36211                        self.write_keyword("DEFAULT");
36212                        self.write_space();
36213                    }
36214                    self.generate_expression(expr)?;
36215                }
36216            } else {
36217                // Single namespace - check if DEFAULT
36218                if !matches!(namespaces.as_ref(), Expression::Alias(_)) {
36219                    self.write_keyword("DEFAULT");
36220                    self.write_space();
36221                }
36222                self.generate_expression(namespaces)?;
36223            }
36224            self.write("), ");
36225        }
36226
36227        // XPath expression
36228        self.generate_expression(&e.this)?;
36229
36230        // PASSING clause - unwrap Tuple to generate comma-separated list without parentheses
36231        if let Some(passing) = &e.passing {
36232            self.write_space();
36233            self.write_keyword("PASSING");
36234            self.write_space();
36235            // Unwrap Tuple if present to avoid extra parentheses
36236            if let Expression::Tuple(tuple) = passing.as_ref() {
36237                for (i, expr) in tuple.expressions.iter().enumerate() {
36238                    if i > 0 {
36239                        self.write(", ");
36240                    }
36241                    self.generate_expression(expr)?;
36242                }
36243            } else {
36244                self.generate_expression(passing)?;
36245            }
36246        }
36247
36248        // RETURNING SEQUENCE BY REF
36249        if e.by_ref.is_some() {
36250            self.write_space();
36251            self.write_keyword("RETURNING SEQUENCE BY REF");
36252        }
36253
36254        // COLUMNS clause
36255        if !e.columns.is_empty() {
36256            self.write_space();
36257            self.write_keyword("COLUMNS");
36258            self.write_space();
36259            for (i, col) in e.columns.iter().enumerate() {
36260                if i > 0 {
36261                    self.write(", ");
36262                }
36263                self.generate_expression(col)?;
36264            }
36265        }
36266
36267        self.write(")");
36268        Ok(())
36269    }
36270
36271    fn generate_xor(&mut self, e: &Xor) -> Result<()> {
36272        // Python: return self.connector_sql(expression, "XOR", stack)
36273        // Handles: this XOR expression or expressions joined by XOR
36274        if let Some(this) = &e.this {
36275            self.generate_expression(this)?;
36276            if let Some(expression) = &e.expression {
36277                self.write_space();
36278                self.write_keyword("XOR");
36279                self.write_space();
36280                self.generate_expression(expression)?;
36281            }
36282        }
36283
36284        // Handle multiple expressions
36285        for (i, expr) in e.expressions.iter().enumerate() {
36286            if i > 0 || e.this.is_some() {
36287                self.write_space();
36288                self.write_keyword("XOR");
36289                self.write_space();
36290            }
36291            self.generate_expression(expr)?;
36292        }
36293        Ok(())
36294    }
36295
36296    fn generate_zipf(&mut self, e: &Zipf) -> Result<()> {
36297        // ZIPF(this, elementcount [, gen])
36298        self.write_keyword("ZIPF");
36299        self.write("(");
36300        self.generate_expression(&e.this)?;
36301        if let Some(elementcount) = &e.elementcount {
36302            self.write(", ");
36303            self.generate_expression(elementcount)?;
36304        }
36305        if let Some(gen) = &e.gen {
36306            self.write(", ");
36307            self.generate_expression(gen)?;
36308        }
36309        self.write(")");
36310        Ok(())
36311    }
36312}
36313
36314impl Default for Generator {
36315    fn default() -> Self {
36316        Self::new()
36317    }
36318}
36319
36320#[cfg(test)]
36321mod tests {
36322    use super::*;
36323    use crate::parser::Parser;
36324
36325    fn roundtrip(sql: &str) -> String {
36326        let ast = Parser::parse_sql(sql).unwrap();
36327        Generator::sql(&ast[0]).unwrap()
36328    }
36329
36330    #[test]
36331    fn test_simple_select() {
36332        let result = roundtrip("SELECT 1");
36333        assert_eq!(result, "SELECT 1");
36334    }
36335
36336    #[test]
36337    fn test_select_from() {
36338        let result = roundtrip("SELECT a, b FROM t");
36339        assert_eq!(result, "SELECT a, b FROM t");
36340    }
36341
36342    #[test]
36343    fn test_select_where() {
36344        let result = roundtrip("SELECT * FROM t WHERE x = 1");
36345        assert_eq!(result, "SELECT * FROM t WHERE x = 1");
36346    }
36347
36348    #[test]
36349    fn test_select_join() {
36350        let result = roundtrip("SELECT * FROM a JOIN b ON a.id = b.id");
36351        assert_eq!(result, "SELECT * FROM a JOIN b ON a.id = b.id");
36352    }
36353
36354    #[test]
36355    fn test_insert() {
36356        let result = roundtrip("INSERT INTO t (a, b) VALUES (1, 2)");
36357        assert_eq!(result, "INSERT INTO t (a, b) VALUES (1, 2)");
36358    }
36359
36360    #[test]
36361    fn test_pretty_print() {
36362        let ast = Parser::parse_sql("SELECT a, b FROM t WHERE x = 1").unwrap();
36363        let result = Generator::pretty_sql(&ast[0]).unwrap();
36364        assert!(result.contains('\n'));
36365    }
36366
36367    #[test]
36368    fn test_window_function() {
36369        let result = roundtrip("SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)");
36370        assert_eq!(
36371            result,
36372            "SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)"
36373        );
36374    }
36375
36376    #[test]
36377    fn test_window_function_with_frame() {
36378        let result = roundtrip("SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
36379        assert_eq!(result, "SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
36380    }
36381
36382    #[test]
36383    fn test_aggregate_with_filter() {
36384        let result = roundtrip("SELECT COUNT(*) FILTER (WHERE status = 1) FROM orders");
36385        assert_eq!(
36386            result,
36387            "SELECT COUNT(*) FILTER(WHERE status = 1) FROM orders"
36388        );
36389    }
36390
36391    #[test]
36392    fn test_subscript() {
36393        let result = roundtrip("SELECT arr[0]");
36394        assert_eq!(result, "SELECT arr[0]");
36395    }
36396
36397    // DDL tests
36398    #[test]
36399    fn test_create_table() {
36400        let result = roundtrip("CREATE TABLE users (id INT, name VARCHAR(100))");
36401        assert_eq!(result, "CREATE TABLE users (id INT, name VARCHAR(100))");
36402    }
36403
36404    #[test]
36405    fn test_create_table_with_constraints() {
36406        let result = roundtrip(
36407            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)",
36408        );
36409        assert_eq!(
36410            result,
36411            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)"
36412        );
36413    }
36414
36415    #[test]
36416    fn test_create_table_if_not_exists() {
36417        let result = roundtrip("CREATE TABLE IF NOT EXISTS t (id INT)");
36418        assert_eq!(result, "CREATE TABLE IF NOT EXISTS t (id INT)");
36419    }
36420
36421    #[test]
36422    fn test_drop_table() {
36423        let result = roundtrip("DROP TABLE users");
36424        assert_eq!(result, "DROP TABLE users");
36425    }
36426
36427    #[test]
36428    fn test_drop_table_if_exists_cascade() {
36429        let result = roundtrip("DROP TABLE IF EXISTS users CASCADE");
36430        assert_eq!(result, "DROP TABLE IF EXISTS users CASCADE");
36431    }
36432
36433    #[test]
36434    fn test_alter_table_add_column() {
36435        let result = roundtrip("ALTER TABLE users ADD COLUMN email VARCHAR(255)");
36436        assert_eq!(result, "ALTER TABLE users ADD COLUMN email VARCHAR(255)");
36437    }
36438
36439    #[test]
36440    fn test_alter_table_drop_column() {
36441        let result = roundtrip("ALTER TABLE users DROP COLUMN email");
36442        assert_eq!(result, "ALTER TABLE users DROP COLUMN email");
36443    }
36444
36445    #[test]
36446    fn test_create_index() {
36447        let result = roundtrip("CREATE INDEX idx_name ON users(name)");
36448        assert_eq!(result, "CREATE INDEX idx_name ON users(name)");
36449    }
36450
36451    #[test]
36452    fn test_create_unique_index() {
36453        let result = roundtrip("CREATE UNIQUE INDEX idx_email ON users(email)");
36454        assert_eq!(result, "CREATE UNIQUE INDEX idx_email ON users(email)");
36455    }
36456
36457    #[test]
36458    fn test_drop_index() {
36459        let result = roundtrip("DROP INDEX idx_name");
36460        assert_eq!(result, "DROP INDEX idx_name");
36461    }
36462
36463    #[test]
36464    fn test_create_view() {
36465        let result = roundtrip("CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1");
36466        assert_eq!(
36467            result,
36468            "CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1"
36469        );
36470    }
36471
36472    #[test]
36473    fn test_drop_view() {
36474        let result = roundtrip("DROP VIEW active_users");
36475        assert_eq!(result, "DROP VIEW active_users");
36476    }
36477
36478    #[test]
36479    fn test_truncate() {
36480        let result = roundtrip("TRUNCATE TABLE users");
36481        assert_eq!(result, "TRUNCATE TABLE users");
36482    }
36483
36484    #[test]
36485    fn test_string_literal_escaping_default() {
36486        // Default: double single quotes
36487        let result = roundtrip("SELECT 'hello'");
36488        assert_eq!(result, "SELECT 'hello'");
36489
36490        // Single quotes are doubled
36491        let result = roundtrip("SELECT 'it''s a test'");
36492        assert_eq!(result, "SELECT 'it''s a test'");
36493    }
36494
36495    #[test]
36496    fn test_not_in_style_prefix_default_generic() {
36497        let result = roundtrip("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')");
36498        assert_eq!(
36499            result,
36500            "SELECT id FROM users WHERE NOT status IN ('deleted', 'banned')"
36501        );
36502    }
36503
36504    #[test]
36505    fn test_not_in_style_infix_generic_override() {
36506        let ast =
36507            Parser::parse_sql("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')")
36508                .unwrap();
36509        let config = GeneratorConfig {
36510            not_in_style: NotInStyle::Infix,
36511            ..Default::default()
36512        };
36513        let mut gen = Generator::with_config(config);
36514        let result = gen.generate(&ast[0]).unwrap();
36515        assert_eq!(
36516            result,
36517            "SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')"
36518        );
36519    }
36520
36521    #[test]
36522    fn test_string_literal_escaping_mysql() {
36523        use crate::dialects::DialectType;
36524
36525        let config = GeneratorConfig {
36526            dialect: Some(DialectType::MySQL),
36527            ..Default::default()
36528        };
36529
36530        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
36531        let mut gen = Generator::with_config(config.clone());
36532        let result = gen.generate(&ast[0]).unwrap();
36533        assert_eq!(result, "SELECT 'hello'");
36534
36535        // MySQL uses SQL standard quote doubling for escaping (matches Python sqlglot)
36536        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
36537        let mut gen = Generator::with_config(config.clone());
36538        let result = gen.generate(&ast[0]).unwrap();
36539        assert_eq!(result, "SELECT 'it''s'");
36540    }
36541
36542    #[test]
36543    fn test_string_literal_escaping_postgres() {
36544        use crate::dialects::DialectType;
36545
36546        let config = GeneratorConfig {
36547            dialect: Some(DialectType::PostgreSQL),
36548            ..Default::default()
36549        };
36550
36551        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
36552        let mut gen = Generator::with_config(config.clone());
36553        let result = gen.generate(&ast[0]).unwrap();
36554        assert_eq!(result, "SELECT 'hello'");
36555
36556        // PostgreSQL uses doubled quotes for regular strings
36557        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
36558        let mut gen = Generator::with_config(config.clone());
36559        let result = gen.generate(&ast[0]).unwrap();
36560        assert_eq!(result, "SELECT 'it''s'");
36561    }
36562
36563    #[test]
36564    fn test_string_literal_escaping_bigquery() {
36565        use crate::dialects::DialectType;
36566
36567        let config = GeneratorConfig {
36568            dialect: Some(DialectType::BigQuery),
36569            ..Default::default()
36570        };
36571
36572        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
36573        let mut gen = Generator::with_config(config.clone());
36574        let result = gen.generate(&ast[0]).unwrap();
36575        assert_eq!(result, "SELECT 'hello'");
36576
36577        // BigQuery escapes single quotes with backslash
36578        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
36579        let mut gen = Generator::with_config(config.clone());
36580        let result = gen.generate(&ast[0]).unwrap();
36581        assert_eq!(result, "SELECT 'it\\'s'");
36582    }
36583}